I have an equivalent of the following model in play scala :
case class Foo(id:Int,value:String)
object Foo{
import play.api.libs.json.Json
implicit val fooFormats = Json.format[Foo]
}
For the following Foo instance
Foo(1, "foo")
I would get the following JSON document:
{"id":1, "value": "foo"}
This JSON is persisted and read from a datastore. Now my requirements have changed and I need to add a property to Foo. The property has a default value :
case class Foo(id:String,value:String, status:String="pending")
Writing to JSON is not a problem :
{"id":1, "value": "foo", "status":"pending"}
Reading from it however yields a JsError for missing the "/status" path.
How can I provide a default with the least possible noise ?
(ps: I have an answer which I will post below but I am not really satisfied with it and would upvote and accept any better option)
An alternative solution is to use
formatNullable[T]
combined withinmap
fromInvariantFunctor
.The cleanest approach that I've found is to use "or pure", e.g.,
This can be used in the normal implicit way when the default is a constant. When it's dynamic, then you need to write a method to create the Reads, and then introduce it in-scope, a la
This probably won't satisfy the "least possible noise" requirement, but why not introduce the new parameter as an
Option[String]
?When reading a
Foo
from an old client, you'll get aNone
, which I'd then handle (with agetOrElse
) in your consumer code.Or, if you don't like this, introduce an
BackwardsCompatibleFoo
:and then turn that one into a
Foo
to work with further on, avoiding to have to deal with this kind of data gymnastics all along in the code.You may define status as an Option
use JsPath like so:
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 :
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 !
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:
The format definition then becomes :
and
will indeed generate an instance of Foo with the default value applied.
This has 2 major flaws in my opinion:
I think the official answer should now be to use the WithDefaultValues coming along Play Json 2.6:
Edit:
It is important to note that the behavior differs from the play-json-extra library. For instance if you have a DateTime parameter that has a default value to DateTime.Now, then you will now get the startup time of the process - probably not what you want - whereas with play-json-extra you had the time of the creation from the JSON.