Search code examples
scalaplay-json

How to update a nested json using scala play framework?


I am trying to update a json value present within a json using Scala play framework.Instead of updating the value it is appending the value.

val newJsonString = """{"P123": 25}"""
val jsonStringAsJsValue = Json.parse("""{"counter_holders": {"Peter": 25}}""")
//jsonStringAsJsValue: play.api.libs.json.JsValue = {"counter_holders":{"Peter":25}}

val jsonTransformer = (__ \"counter_holders" ).json.update(__.read[JsValue].map{o => Json.parse(newJsonString)})

jsonStringAsJsValue.transform(jsonTransformer).get.as[JsValue]
//Now getting this jsvalue
//play.api.libs.json.JsValue = {"counter_holders":{"Peter":25,"P123":25}}
//But need  this jsvalue
//play.api.libs.json.JsValue = {"counter_holders":{"P123":25}}

Any help on this will be really nice.


Solution

  • Quoting from the update method docs:

    (__ \ 'key).json.update(reads) is the most complex Reads[JsObject] but the most powerful:

    • copies the whole JsValue => A

    • applies the passed Reads[A] on JsValue => B

    • deep merges both JsValues (A ++ B) so B overwrites A identical branches Please note that if you have prune a branch in B, it is still in A so you'll see it in the result Example:

      {{{ val js = Json.obj("key1" -> "value1", "key2" -> "value2")
      js.validate(__.json.update((__ \ 'key3).json.put(JsString("value3"))))
      => JsSuccess({"key1":"value1","key2":"value2","key3":"value3"},) }}}
      

    Therefore the behaviour you see is as expected. If you want to take that approach, of updating using the path, you can use the method prune. For example you can do:

    val newJsonString = """{"P123": 25}"""
    val jsonStringAsJsValue = Json.parse("""{"counter_holders": {"Peter": 25}}""")
    //jsonStringAsJsValue: play.api.libs.json.JsValue = {"counter_holders":{"Peter":25}}
    
    val jsonTransformer = (__ \"counter_holders" ).json
      .update(__.read[JsValue].map{o => Json.parse(newJsonString)})
    
    val jsonTransformerDelete = (__ \"counter_holders" \ "Peter" ).json.prune
    
    jsonStringAsJsValue.transform(jsonTransformer).flatMap(_.transform(jsonTransformerDelete)) match {
      case JsSuccess(value, _) =>
        println(value)
      case JsError(errors) =>
        println(errors)
    }
    

    which will produce the wanted behaviour. You can find it in scastie.