What's the difference between Writes[-A] and OWrites[-A] in PlayFramework Json library? I have used Writes[A] but I can't figure out what's the purpose of OWrites. The same question applies to Format[A] vs OFormat[A].
The source code can be found here for Writes and here for Formats. I have looked at them but I can't figure out the difference on their usages.
Often you know that an encoder will always produce a JSON object (as opposed to an arbitrary JSON value). Tracking this fact in the type system makes it possible to work with the output of such an encoder without jumping through the hoops that would be normally be necessary.
For example, suppose we've got a simple class:
class Foo(val name: String, val age: Long)
And we write a Writes
instance like this:
import play.api.libs.functional.syntax._
import play.api.libs.json._
implicit val fooWrites: Writes[Foo] = (
(__ \ 'name').write[String] and (__ \ 'age').write[Long]
)(foo => (foo.name, foo.age))
Now we can write the following:
scala> val json = fooWrites.writes(new Foo("McBar", 101))
json: play.api.libs.json.JsValue = {"name":"McBar","age":101}
Now suppose that for whatever reason we want to get a list of the field names. We have to write something like this:
scala> json.as[JsObject].keys
res0: scala.collection.Set[String] = Set(name, age)
Instead of this:
scala> json.keys
<console>:17: error: value keys is not a member of play.api.libs.json.JsValue
json.keys
^
But of course we know that json
will always be a JsObject
. The problem is that the compiler doesn't. OWrites
fixes this:
implicit val fooWrites: OWrites[Foo] = (
(__ \ 'name').write[String] and (__ \ 'age').write[Long]
)(foo => (foo.name, foo.age))
And then:
scala> val json = fooWrites.writes(new Foo("McBar", 101))
json: play.api.libs.json.JsObject = {"name":"McBar","age":101}
scala> json.keys
res1: scala.collection.Set[String] = Set(name, age)
The output of writes
on OWrites
is statically typed as a JsObject
, so we can use .keys
without the unsafe as[JsObject]
cast.
(As a side note, I'm not personally a fan of making method return types more specific in subclasses, and I've taken a slightly different approach to solving this problem in circe.)