Search code examples
jsonscalaplay-json

Add additional fields after de-serialization of json using play-json-extensions


I am having a case class that has more than 22 parameters.

case class Model(a1: Int,
                   a2: Int,
                   a3: Int,
                   a4: Int,
                   a5: Int,
                   a6: Int,
                   a7: Int,
                   a8: Int,
                   a9: Int,
                   a10: Int,
                   a11: Int,
                   a12: Int,
                   a13: Int,
                   a14: Int,
                   a15: Int,
                   a16: Int,
                   a17: Int,
                   a18: Int,
                   a19: Int,
                   a20: Int,
                   a21: Int,
                   a22: Int,
                   a23: Int,
                   a24: Int)

I am getting a json input which I need to de-serialize to the above Model case class. But, my input json does not contain the fields a1 and a2. My json looks something like this

  {
  "a3": 3,
  "a4": 4,
  "a5": 5,
  "a6": 6,
  "a7": 7,
  "a8": 8,
  "a9": 9,
  "a10": 10,
  "a11": 11,
  "a12": 12,
  "a13": 13,
  "a14": 14,
  "a15": 15,
  "a16": 16,
  "a17": 17,
  "a18": 18,
  "a19": 19,
  "a20": 20,
  "a21": 21,
  "a22": 22,
  "a23": 23,
  "a24": 24
}

Now, to cater to this situation, I wrote my custom json reader which would add some dummy values for the fields a1 and a2 in the json.

I have a custom method to add fields to the input json that is read. Extension method addField is as follows

implicit class ReadOps[A](reads: Reads[A]) {
    def addField(fieldName: String, value: Int): Reads[A] = Reads {
      json: JsValue =>
        json
          .validate(__.json.update((__ \ fieldName).json.put(JsString(value.toString))))
          .map(_.asInstanceOf[A])
    }
  }

My implicit json format is as follows

 implicit val jsonFormat = new OFormat[Model] {

    override def reads(json: JsValue): JsResult[Model] = {
      Jsonx.formatCaseClass[Model].addField("a1",1).addField("a2",2).reads(json)
    }

    override def writes(o: Model): JsObject = {
      Jsonx.formatCaseClass[Model].writes(o)
    }
  }

Problem that I am facing

Inspite of calling the method addField twice (I am trying to add a1 with value 1 and a2 with value 2), only the field a1 with value 1 gets added to the json and a2 is ignored. And the json does not get de-serialized to the model case class.

I am using the following dependency

libraryDependencies += "ai.x" %% "play-json-extensions" % "0.30.1"

Please let me know where I am going wrong in this approach. Any pointers will be very helpful. Thanks in advance !!!

Note:- Please don't suggest solutions like adding a1 and a2 as fields in the model case class. This is out of question as my actual problem is quite complicated and this question on stackoverflow is a very simplified version of the problem at hand.


Solution

  • Consider adding a field in a new branch via JsPath.update:

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

    Here is a working example

    import ai.x.play.json.Jsonx
    import play.api.libs.json.Json
    import play.api.libs.json._
    
    case class Foo(a1: Int, a2: Int, a3: Int, a4: Int, a5: Int, a6: Int, a7: Int, a8: Int, a9: Int, a10: Int, a11: Int, a12: Int, a13: Int, a14: Int, a15: Int, a16: Int, a17: Int, a18: Int, a19: Int, a20: Int, a21: Int, a22: Int, a23: Int, a24: Int)
    
    object Foo {
      implicit val format = Jsonx.formatCaseClass[Foo]
    }
    
    object AddFieldsToDeserialisation extends App {
      val raw =
        """
          |{
          |  "a3": 3,
          |  "a4": 4,
          |  "a5": 5,
          |  "a6": 6,
          |  "a7": 7,
          |  "a8": 8,
          |  "a9": 9,
          |  "a10": 10,
          |  "a11": 11,
          |  "a12": 12,
          |  "a13": 13,
          |  "a14": 14,
          |  "a15": 15,
          |  "a16": 16,
          |  "a17": 17,
          |  "a18": 18,
          |  "a19": 19,
          |  "a20": 20,
          |  "a21": 21,
          |  "a22": 22,
          |  "a23": 23,
          |  "a24": 24
          |}
        """.stripMargin
    
    
      val json = Json.parse(raw)
      val updatePutTransformation = 
        (field: String, value: Int) =>  __.json.update((__ \ field ).json.put(JsNumber(value)))
      val addedFields =
        List("a1" -> 1, "a2" -> 2)
          .map { case (field, value) => updatePutTransformation(field, value) }
          .reduce((putUpdate1, putUpdate2) => putUpdate1 andThen putUpdate2 )
      val jsonWithPutValue = json.transform(addedFields)
      println(jsonWithPutValue.get)
      println(jsonWithPutValue.get.as[Foo])
    
    
    }
    

    should output

    { "a1": 1, "a2": 2, "a3": 3, "a4": 4, "a5": 5, "a6": 6, "a7": 7, "a8": 8, "a9": 9, "a10": 10, "a11": 11, "a12": 12, "a13": 13, "a14": 14, "a15": 15, "a16": 16, "a17": 17, "a18": 18, "a19": 19, "a20": 20, "a21": 21, "a22": 22, "a23": 23, "a24": 24 }
    
    Foo(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24)