Search code examples
scalajson4s

json4s: cross field extraction


json4s extract JsonAST based on per-type rules defined in DefaultFormats and CustomSerializer.

Sometimes I want to have cross field extraction. For example, given a json string {"a": 1, "b": 2}, I want to set the value of b into a+b. I can do:

import org.json4s._
import org.json4s.native.JsonMethods._
import org.json4s.JsonAST._

case class A(a: Int, b: Int)

case object ACustomSerializer extends CustomSerializer[A](
    format =>
    ({
        case jo: JObject =>
            val a = (jo \ "a").extract[Int]
            val b = (jo \ "b").extract[Int] + a
            A(a, b)
    }, Map())
)

implicit val formats = DefaultFormats + ACustomSerializer

parse("""{"a": 1, "b": 2}""").extract[A] // A(1,3)

However, if case class A has many other fields, it becomes hard to write rule for all of them.

case class A(a: Int, b: Int, c: Int, d: Int)

case object ACustomSerializer extends CustomSerializer[A](
    format =>
    ({
        case jo: JObject =>
            val a = (jo \ "a").extract[Int]
            val b = (jo \ "b").extract[Int] + a
            val c = ...
            val d = ...
            A(a, b, c, d)
    }, Map())
)

They could have been handled by DefaultFormats or other CustomSerializer if we don't want to have "cross field extraction" for field b. Things get worse if the case class is actually large.

Is there a way to write rule for special fields only, and leave the rest handled by DefaultFormats or CustomSerialzer?


Solution

  • In general it is best to parse data as-is and then process afterwards (in order to maintain separation of concerns).

    In this case it looks like this:

    val a = parse("""{"a": 1, "b": 2}""").extract[A] // A(1,2)
    
    a.copy(b = a.a + a.b) // A(1,3)
    

    In more complex cases the layout of the processed data will differ from the parsed data so you will need a second case class that describes raw data and a function to convert this to the processed format. While this may seem cumbersome, it will make the code easier to understand and more amenable to modification.