playframework / playframework/play-json
JsPath.json.put writing to an object in a nested array exception: expected KeyPathNode
Nobody has claimed this yet.
- Dominant language
- Scala
- Stars
- 375
- Forks
- 140
- Avg merge
- 1d 5h
- Merged PRs (30d)
- 28
Description
Play JSON Version (2.5.x / etc)
2.5.15
API (Scala / Java / Neither / Both)
Scala (didn't test Java)
Operating System
Window 10
JDK
java version "1.8.0_121"
Java(TM) SE Runtime Environment (build 1.8.0_121-b13)
Java HotSpot(TM) 64-Bit Server VM (build 25.121-b13, mixed mode)
Library Dependencies
Expected Behavior
I would expect to use a JsPath.json.update and JsPath.json.put to update a single field within an object within a nested array. (See code below)
Actual Behavior
When attempting to put a new field in a JsObject that's inside an array, a RuntimeException is generated.
The following code throws an exception:
expected KeyPathNode
java.lang.RuntimeException: expected KeyPathNode
at play.api.libs.json.JsPath$.step$1(JsPath.scala:142)
at play.api.libs.json.JsPath$.step$1(JsPath.scala:141)
at play.api.libs.json.JsPath$.play$api$libs$json$JsPath$$buildSubPath$1(JsPath.scala:147)
at play.api.libs.json.JsPath$$anonfun$createObj$1.apply(JsPath.scala:152)
at play.api.libs.json.JsPath$$anonfun$createObj$1.apply(JsPath.scala:150)
at scala.collection.IndexedSeqOptimized$class.foldl(IndexedSeqOptimized.scala:57)
at scala.collection.IndexedSeqOptimized$class.foldLeft(IndexedSeqOptimized.scala:66)
at scala.collection.mutable.WrappedArray.foldLeft(WrappedArray.scala:35)
at play.api.libs.json.JsPath$.createObj(JsPath.scala:150)
at play.api.libs.json.PathReads$$anonfun$jsPut$1.apply(JsConstraints.scala:63)
at play.api.libs.json.PathReads$$anonfun$jsPut$1.apply(JsConstraints.scala:63)
at play.api.libs.json.Reads$$anon$8.reads(Reads.scala:126)
at play.api.libs.json.PathReads$$anonfun$jsUpdate$1$$anonfun$apply$11.apply(JsConstraints.scala:72)
at play.api.libs.json.PathReads$$anonfun$jsUpdate$1$$anonfun$apply$11.apply(JsConstraints.scala:72)
at play.api.libs.json.JsResult$class.flatMap(JsResult.scala:99)
at play.api.libs.json.JsSuccess.flatMap(JsResult.scala:9)
at play.api.libs.json.PathReads$$anonfun$jsUpdate$1.apply(JsConstraints.scala:72)
at play.api.libs.json.PathReads$$anonfun$jsUpdate$1.apply(JsConstraints.scala:69)
at play.api.libs.json.Reads$$anon$8.reads(Reads.scala:126)
at play.api.libs.json.JsValue$class.validate(JsValue.scala:18)
at play.api.libs.json.JsObject.validate(JsValue.scala:76)
at play.api.libs.json.JsReadable$class.transform(JsReadable.scala:29)
at play.api.libs.json.JsObject.transform(JsValue.scala:76)
private def setValueByPath(data: JsObject, path: JsPath, value: JsValue) : JsObject = {
data.transform(__.json.update(path.json.put({ value }))) match {
case s: JsSuccess[JsObject] => s.get
case e: JsError => throw new IllegalArgumentException("Failed")
}
}
val data = Json.obj(
"array" -> Json.arr(Json.obj())
)
val newData = setValueByPath(data, __ \ "array" \ 0 \ "field", Json.toJson("arrayField"))
Reproducible Test Case
package jspathtest
import org.scalatest._
import play.api.libs.json._
class TestJsPath extends FlatSpec with Matchers {
"A JsPath" should "allow setting a value in a nested branch in array" in {
val data = Json.obj(
"array" -> Json.arr(Json.obj())
)
val newData = setValueByPath(data, __ \ "array" \ 0 \ "field", Json.toJson("arrayField"))
newData should be (Json.obj("array" -> Json.arr(Json.obj("field" -> "arrayField"))))
}
it should "allow setting a value in a root field" in {
val data = Json.obj(
"array" -> Json.arr(Json.obj())
)
val newData = setValueByPath(data, __ \ "field", Json.toJson("field"))
newData should be (Json.obj("field" -> "field", "array" -> Json.arr(Json.obj())))
}
it should "allow setting a value in a nested obj field" in {
val data = Json.obj(
"other" -> Json.obj()
)
val newData = setValueByPath(data, __ \ "other" \ "field", Json.toJson("otherField"))
newData should be (Json.obj("other" -> Json.obj("field" -> "otherField")))
}
private def setValueByPath(data: JsObject, path: JsPath, value: JsValue) : JsObject = {
data.transform(__.json.update(path.json.put({ value }))) match {
case s: JsSuccess[JsObject] => s.get
case e: JsError => throw new IllegalArgumentException("Failed")
}
}
}
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with the failing path construction in JsPath.scala around lines 141-152 and the jsPut handling in JsConstraints.scala:63. Add the reproducible ScalaTest case from the issue to the project tests, run the relevant test suite, and confirm that updating a field inside an object nested in an array produces the expected JsObject without the exception.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- scala
- Domain
- backend
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Stale
- Clarity
- Clearly specified
- Newbie friendliness
- 38/100