Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JsPath.json.put writing to an object in a nested array exception: expected KeyPathNode #82

Open
noisypants opened this issue Jul 14, 2017 · 7 comments

Comments

@noisypants
Copy link

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")
    }
  }
}

@noisypants
Copy link
Author

After further looking into this, it seems that the JsPath.createObj method does not support IdxPathNode elements in the path. Can someone who knows this code please confirm, and maybe offer a workaround?
Thanks

@gmethvin
Copy link
Member

@noisypants You're correct, this is a bug in JsPath.createObj. I believe it needs to be rewritten in order to deal with types other than objects. Currently we expect to be able to use JsObject.deepMerge to merge all the sub-trees from the paths at once. This doesn't work for arrays since we don't merge arrays in deepMerge; for array keys we simply replace the array.

I think the only way to fix this is to update the implementation in createObj to actually do the merging itself rather than attempting to delegate to deepMerge. @cchantep what do you think?

@rators
Copy link

rators commented Feb 7, 2019

any progress on this issue?

@agolovenko
Copy link

any update?

@agolovenko
Copy link

it is still a thing after 3 years

@rcongiu
Copy link

rcongiu commented Dec 7, 2020

I am hitting the same issue

@sachag678
Copy link

Any thoughts on how to work around this issue? Even the suggested other libs such as play-json-zipper are no longer being maintained.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

7 participants