Search code examples
scalajson4s

json4s extractor for partial fields (when we care to preserve the original Json)


When parsing a Json String, there are cases where we are interested in parsing just a subset of the fields (the ones we know we need) and delay any semantic parsing/mapping of the rest of the fields.

E.g. we want inspect the key in a key-value Event for (say) load-balancing, but at that point in time we don't have enough info to completely parse the values (we assume that some application further down the road will know what to do with the values):

val json = """{ "key": {"a": true, "b": 123}, "value": [1,2,3,4] }"""

case class Key(a: Boolean, b: Int)
case class Event(key: Key, value: ???) // value can be any valid Json

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

implicit val formats = DefaultFormats

val parsedJson = parse(json)

parsedJson.extract[Event]

The question is how to represent the fields that we don't yet know (or care) to parse? What do we add as the type for value?

Note: One solution is to change the event to be case class Event(key: Key). This will work, but it will completely ignore the value, which we ideally want to keep so we can dispatch it properly to another service. So this solution will not work for us.


Solution

  • parse() parses JSON into json4s AST and returns JValue.

    AFAIK You can not parse JSON partially because parsing to AST includes JSON validation and for that you need to parse the whole JSON string to AST tree.

    But you can partially extract from AST. Here you have two options.

    First. Make value field a JValue to defer extracting. You can do it later by calling extract() on this JValue instance.

    case class Event(key: Key, value: JValue)
    
    val event = parsedJson.extract[Event]
    val value = event.value.extract[Seq[Int]]
    

    Second. Split Event to two classes and extract them separately.

    case class EventKey(key: Key)
    case class EventValue(value: Seq[Int])
    
    val parsedJson = parse(json)
    val eventKey = parsedJson.extract[EventKey]
    val eventValue = parsedJson.extract[EventValue]