Defaults for missing properties in play 2 JSON formats

Play 2.6+

As per @CanardMoussant’s answer, starting with Play 2.6 the play-json macro has been improved and proposes multiple new features including using the default values as placeholders when deserializing :

implicit def jsonFormat = Json.using[Json.WithDefaultValues].format[Foo]

For play below 2.6 the best option remains using one of the options below :

play-json-extra

I found out about a much better solution to most of the shortcomings I had with play-json including the one in the question:

play-json-extra which uses [play-json-extensions] internally to solve the particular issue in this question.

It includes a macro which will automatically include the missing defaults in the serializer/deserializer, making refactors much less error prone !

import play.json.extra.Jsonx
implicit def jsonFormat = Jsonx.formatCaseClass[Foo]

there is more to the library you may want to check: play-json-extra

Json transformers

My current solution is to create a JSON Transformer and combine it with the Reads generated by the macro. The transformer is generated by the following method:

object JsonExtensions{
  def withDefault[A](key:String, default:A)(implicit writes:Writes[A]) = __.json.update((__ \ key).json.copyFrom((__ \ key).json.pick orElse Reads.pure(Json.toJson(default))))
}

The format definition then becomes :

implicit val fooformats: Format[Foo] = new Format[Foo]{
  import JsonExtensions._
  val base = Json.format[Foo]
  def reads(json: JsValue): JsResult[Foo] = base.compose(withDefault("status","bidon")).reads(json)
  def writes(o: Foo): JsValue = base.writes(o)
}

and

Json.parse("""{"id":"1", "value":"foo"}""").validate[Foo]

will indeed generate an instance of Foo with the default value applied.

This has 2 major flaws in my opinion:

  • The defaulter key name is in a string and won’t get picked up by a refactoring
  • The value of the default is duplicated and if changed at one place will need to be changed manually at the other

Leave a Comment