Search code examples
jsonscalajson4s

What's the most elegant way to deconstruct a JSON array with JSON4s?


I have to deconstruct the following JSON into a list of case classes:

{
  "data": [
    [49, true, 14, null, null],
    [52, false, null, null, null],
    [72, true, 4, 2, 1]
  ]
}

case class:

case class Data(i1: Int, b: Bool, i2: Option[Int], i3: Option[Int], i4: Option[Int])

I started with a for comprehension, but was not able to finish it:

for {
  JArray(data) <- json \ "data"
  JArray(d) <- data
  JInt(line) <- d.head // ???
} yield Data()

Any help is much appreciated.

Thanks,

Michael


Solution

  • You can write a CustomSerializer for Data.

    I introduced a JOptionInt extractor to turn a JInt or a JNull into a Option[Int], it is possible it can be done in json4s directly.

    import org.json4s._
    import org.json4s.jackson.JsonMethods._
    import org.json4s.JsonDSL._
    
    case class Data(i1: Int, b: Boolean, i2: Option[Int], i3: Option[Int], i4: Option[Int])
    
    object DataSerializer extends CustomSerializer[Data]( format => ( 
      {
        case JArray(List(JInt(i1), JBool(b), JOptionInt(i2), JOptionInt(i3), JOptionInt(i4))) =>
          Data(i1.toInt, b, i2, i3 , i4) 
      }, {
        case d: Data => JArray(List(d.i1, d.b, d.i2, d.i3, d.i4)) 
      }  
    ))
    
    object JOptionInt {
      def unapply(x: JValue) : Option[Option[Int]] = x match {
        case JInt(i) => Option(Option(i.toInt))
        case JNull   => Option(None)
        case _       => None
      }
    }
    

    Which can be used as:

    implicit val formats = DataSerializer
    
    val json = parse("""
          {
            "data": [
              [49, true, 14, null, null],
              [52, false, null, null, null],
              [72, true, 4, 2, 1]
            ]
          }
          """)
    
    val result = (json \ "data").extract[Array[Data]]
    // Array(Data(49,true,Some(14),None,None), Data(52,false,None,None,None), Data(72,true,Some(4),Some(2),Some(1)))