Search code examples
jsonscalaspray-json

Spray-json deserializing nested object


How to deserialize nested objects correctly in spray-json?

    import spray.json._

    case class Person(name: String)

    case class Color(n: String, r: Int, g: Int, b: Int, p: Person)

    object MyJsonProtocol extends DefaultJsonProtocol {

      implicit object ColorJsonFormat extends RootJsonFormat[Color] {
        def write(c: Color) = JsObject(
          "color-name" -> JsString(c.n),
          "Green" -> JsNumber(c.g),
          "Red" -> JsNumber(c.r),
          "Blue" -> JsNumber(c.b),
          "person-field" -> JsObject("p-name" -> JsString(c.p.name))
        )

        def read(value: JsValue) = {
          value.asJsObject.getFields("color-name", "Red", "Green", "Blue", "person-field") match {
            case Seq(JsString(name), JsNumber(red), JsNumber(green), JsNumber(blue), JsObject(person)) =>
              Color(name, red.toInt, green.toInt, blue.toInt, null) //gotta replace null with correct deserializer
            case _ => throw new DeserializationException("Color expected")
          }
        }
      }

    }

    import MyJsonProtocol._

    val jsValue = Color("CadetBlue", 95, 158, 160, Person("guest")).toJson

    jsValue.prettyPrint

    val color = jsValue.convertTo[Color] //person is missing of course

On a side-note, how to spray-json help serializing to a map of fields (with nested map for nested objects)?


Solution

  • The example below demonstrates JSON -> Abstract Syntax Tree -> Scala Case Classes and back with custom field names and support for optional case class members. The example is derived from the spray-json documentation at https://github.com/spray/spray-json for version 1.2.5.

    package rando
    
    import spray.json._
    
    case class Color(name: String, red: Int, green: Int, blue: Int)
    
    case class Team(name: String, color: Option[Color])
    
    object MyJsonProtocol extends DefaultJsonProtocol {
      implicit val colorFormat = jsonFormat(Color, "name", "r", "g", "b")
      implicit val teamFormat = jsonFormat(Team, "name", "jersey")
    }
    import MyJsonProtocol._
    
    object GoSox extends App {
      val obj = Team("Red Sox", Some(Color("Red", 255, 0, 0)))
      val ast = obj.toJson
      println(obj)
      println(ast.prettyPrint)
      println(ast.convertTo[Team])
      println("""{ "name": "Red Sox", "jersey": null }""".asJson.convertTo[Team])
      println("""{ "name": "Red Sox" }""".asJson.convertTo[Team])
    }
    

    The example outputs the following when executed:

    Team(Red Sox,Some(Color(Red,255,0,0)))
    {
      "name": "Red Sox",
      "jersey": {
        "name": "Red",
        "r": 255,
        "g": 0,
        "b": 0
      }
    }
    Team(Red Sox,Some(Color(Red,255,0,0)))
    Team(Red Sox,None)
    Team(Red Sox,None)