Search code examples
scalaplayframeworkplay-json

How to update each field in a list using Play json in Scala


I have the following json string:

{
   "id":123,
   "students":[
      {
         "collected":{
            "field":"field_1"
         },
         "attr":[{
            "name":"test_name",
            "age":"17",
            "color":"blue"
         }]
      }
   ]
}

I want to update all the elements in "attr" object in all of my "students" list. By updating, I mean that I need to update the values into the concatenated string of the key and value

{
   "id":123,
   "students":[
      {
         "collected":{
            "field":"field_1"
         },
         "attr":[{
            "name":"nametest_name",
            "age":"age17",
            "color":"colorblue"
         }]
      }
   ]
}

I have come across into transform method of JsValue. This is my transformer:

val jsonTransformer = (__ \ 'students).json.update(
  of[JsArray].map{
    case JsArray(list) =>
      list.map(o => o)
  }
)

My transformer is not changing anything because I can't seem to find a way to traverse to the "attr" field.

Can anyone provide me an insight?


Solution

  • Taking following as input (where attr is an array of objects):

       val input =
          """
            |{
            |   "id":123,
            |   "students":[
            |      {
            |         "collected":{
            |            "field":"field_1"
            |         },
            |         "attr":[{
            |            "name":"test_name",
            |            "age":"17",
            |            "color":"blue"
            |         }]
            |      },
            |      {
            |         "collected":{
            |            "field":"field_2"
            |         },
            |         "attr":[{
            |            "name":"test_name2",
            |            "age":"18",
            |            "color":"red"
            |         }]
            |      }
            |   ]
            |}
            |""".stripMargin
    

    We can apply following transformer to get the required results:

        val attrTransformer = (__ \ "attr").json.update {
          __.read[JsArray].map {
            case JsArray(values) =>
    
              val updatedValues = values.map { x =>
                JsObject(x.as[JsObject].fields.map { z =>
                  val (key, value) = z
                  (key, JsString(key + value.as[String]))
                })
              }
    
              JsArray(updatedValues)
          }
        }
    
        val transformer = (__ \ "students").json.update(Reads.list(attrTransformer).map(x => JsArray(x)))
    
        val output = json.transform(transformer).get
    

    The output after transformation will be:

    {
      "students" : [ {
        "collected" : {
          "field" : "field_1"
        },
        "attr" : [ {
          "name" : "nametest_name",
          "age" : "age17",
          "color" : "colorblue"
        } ]
      }, {
        "collected" : {
          "field" : "field_2"
        },
        "attr" : [ {
          "name" : "nametest_name2",
          "age" : "age18",
          "color" : "colorred"
        } ]
      } ],
      "id" : 123
    }
    

    Old Answer

    Considering the below input (where attr is not an array of JsObject):

        val input =
          """
            |{
            |   "id":123,
            |   "students":[
            |      {
            |         "collected":{
            |            "field":"field_1"
            |         },
            |         "attr":{
            |            "name":"test_name",
            |            "age":"17",
            |            "color":"blue"
            |         }
            |      },
            |      {
            |         "collected":{
            |            "field":"field_2"
            |         },
            |         "attr":{
            |            "name":"test_name2",
            |            "age":"18",
            |            "color":"red"
            |         }
            |      }
            |   ]
            |}
            |""".stripMargin
    

    A simple solution would be to create a new JsObject with updated values as below. (without any validations)

        val students = (Json.parse(input) \ "students").as[JsArray]
        val requiredStudents = students.value.map { student =>
          val attr = student \ "attr"
    
          val updatedAttributes = attr.get.as[JsObject].fields.map { x =>
            val (key, value) = x
            (key, JsString(key + value.as[String]))
          }
    
          val requiredStudent = student.as[JsObject] ++ Json.obj("attr" -> JsObject(updatedAttributes))
          requiredStudent
        }
    
        requiredStudents.foreach(println)
    

    The output for each student will be as follows:

    {"collected":{"field":"field_1"},"attr":{"name":"nametest_name","age":"age17","color":"colorblue"}}
    {"collected":{"field":"field_2"},"attr":{"name":"nametest_name2","age":"age18","color":"colorred"}}