Explicitly output JSON null in case of missing optional value

Greg Methvin, a Play committer, wrote this answer to me in a related GitHub issue:

The JSON macros only support one way of encoding optional values, which is to omit None values from the JSON. This is not a bug but rather a limitation of the implementation. If you want to include nulls you're unfortunately going to have to implement your own Writes.

I do think we should try to provide more configurability for the macros though.

In this case, I'll let Play exclude this field when the value is null, even if it slightly sacrifices API consistency and self-documentability. It's still such a minor thing (in this particular API) that it doesn't warrant uglifying the code as much as a custom Writes would take for a case class with a dozen values.

I'm hoping they do make this more configurable in future Play versions.


Here's a way to do it:

object MyWrites extends DefaultWrites{

  override def OptionWrites[T](implicit fmt: Writes[T]): Writes[Option[T]] = new Writes[Option[T]] {
    override def writes(o: Option[T]): JsValue = {
      o match {
        case Some(a) => Json.toJson(a)(fmt)
        case None => JsNull
      }
    }
  }
}

This will overwrite the default implementation which will not create an element. I used this in your sample code:

case class FooJson(
                // ...
                location: Option[LocationJson]
              )

case class LocationJson(latitude: Double, longitude: Double)

object LocationJson {
  implicit val writes = Json.writes[LocationJson]
}

implicit val fooJsonWriter: Writes[FooJson] = new Writes[FooJson] {
  override def writes(o: FooJson): JsValue = {
    JsObject(Seq(
      "location" -> Json.toJson(o.location)
      // Additional fields go here.
    ))
  }
}

Json.toJson(FooJson(None))

And got this result res0: play.api.libs.json.JsValue = {"location":null}.


Hello from the future.

As of Play 2.7, a fairly simple solution was introduced for automated JSON codecs. We can introduce the appropriate implicit value for JsonConfiguration in the scope for the Format/Reads/Writes. The following configuration will write nulls for empty Options instead of omitting the fields entirely.

import play.api.libs.json._

implicit val config = JsonConfiguration(optionHandlers = OptionHandlers.WritesNull)
implicit val residentWrites = Json.writes[Resident]

Reference