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
?
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.