Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Outputting 'null' for Option[T] in play-json serialization when value is None

I'm using play-json's macros to define implicit Writes for serializing JSON. However, it seems like by default play-json omits fields for which Option fields are set to None. Is there a way to change the default so that it outputs null instead? I know this is possible if I define my own Writes definition, but I'm interested in doing it via macros to reduce boilerplate code.

Example

case class Person(name: String, address: Option[String])

implicit val personWrites = Json.writes[Person]    
Json.toJson(Person("John Smith", None))

// Outputs: {"name":"John Smith"}
// Instead want to output: {"name":"John Smith", "address": null}
like image 848
Dia Kharrat Avatar asked Mar 05 '14 07:03

Dia Kharrat


3 Answers

The Json.writes macro generates a writeNullable[T] for optional fields. Like you know (or not), writeNullable[T] omits the field if the value is None, whereas write[Option[T]] generates a null field.

Defining a custom writer is the only option you have to get this behavior.

( 
  (__ \ 'name).write[String] and
  (__ \ 'address).write[Option[String]]
)(unlift(Person.unapply _))
like image 118
Julien Lafont Avatar answered Oct 13 '22 00:10

Julien Lafont


You can use a custom implicit JsonConfiguration, see Customize the macro to output null

implicit val config = JsonConfiguration(optionHandlers = OptionHandlers.WritesNull)

implicit val personWrites = Json.writes[Person]    
Json.toJson(Person("John Smith", None))
like image 26
Stefan Schönbächler Avatar answered Oct 12 '22 22:10

Stefan Schönbächler


Not a real solution for you situation. But slightly better than having to manually write the writes

I created a helper class that can "ensure" fields.

implicit class WritesOps[A](val self: Writes[A]) extends AnyVal {
    def ensureField(fieldName: String, path: JsPath = __, value: JsValue = JsNull): Writes[A] = {
      val update = path.json.update(
        __.read[JsObject].map( o => if(o.keys.contains(fieldName)) o else o ++ Json.obj(fieldName -> value))
      )
      self.transform(js => js.validate(update) match {
        case JsSuccess(v,_) => v
        case err: JsError => throw new JsResultException(err.errors)
      })
    }

    def ensureFields(fieldNames: String*)(value: JsValue = JsNull, path: JsPath = __): Writes[A] =
      fieldNames.foldLeft(self)((w, fn) => w.ensureField(fn, path, value))

}

so that you can write

Json.writes[Person].ensureFields("address")()
like image 24
KailuoWang Avatar answered Oct 12 '22 23:10

KailuoWang