6

Here is spray-json example. Here is NullOptions trait.

The problem is when I declare a case class say

object MyJsonProtocol extends DefaultJsonProtocol  {
  implicit val some: RootJsonFormat[Some] = jsonFormat2(Some)
}

case class Some (
                name:String,
                age:Int
                )

and json do not contains a field for example:

{
    "name":"John"
}

I get: java.util.NoSuchElementException: key not found: age

So I have to add an Option and NullOption trait like that:

object MyJsonProtocol extends DefaultJsonProtocol with NullOptions  {
  implicit val some: RootJsonFormat[Some] = jsonFormat2(Some)
}

case class Some (
                name:String,
                age:Option[Int]
                )

Everything works. But I do not want to have a case classes where all member are Option. Is there a way to configure spray json unmarshalling to just set nulls without additional Option type?

P.S.

I understand that in general Option is better then null check, but in my case it is just monkey code.

Also complete example of marshalling during response processing is here

Cherry
  • 31,309
  • 66
  • 224
  • 364

2 Answers2

2

The only way I can think of is to implement your own Protocol via read/write, which might be cumbersome. Below is a simplified example. Note that I changed the age to be an Integer instead of an Int since Int is an AnyVal, which is not nullable by default. Furthermore, I only consider the age field to be nullable, so you might need to adopt as necessary. Hope it helps.

 case class Foo (name:String, age: Integer)

 object MyJsonProtocol extends DefaultJsonProtocol {
    implicit object FooJsonFormat extends RootJsonFormat[Foo] {
      def write(foo: Foo) =
        JsObject("name" -> JsString(foo.name),
                 "age"  -> Option(foo.age).map(JsNumber(_)).getOrElse(JsNull))

      def read(value: JsValue) = value match {
        case JsObject(fields) =>
          val ageOpt: Option[Integer] = fields.get("age").map(_.toString().toInt) // implicit conversion from Int to Integer
          val age: Integer = ageOpt.orNull[Integer]
          Foo(fields.get("name").get.toString(), age)
        case _ => deserializationError("Foo expected")
      }
    }
  }

  import MyJsonProtocol._
  import spray.json._

  val json = """{ "name": "Meh" }""".parseJson
  println(json.convertTo[Foo]) // prints Foo("Meh",null)
edi
  • 3,112
  • 21
  • 16
1

It seems you're out of luck

From the doc you linked:

spray-json will always read missing optional members as well as null optional members as None

You can customize the json writing, but not the reading.

Gabriele Petronella
  • 106,943
  • 21
  • 217
  • 235