28

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.

vicaba
  • 2,836
  • 1
  • 27
  • 45

1 Answers1

42

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.)

Valy Dia
  • 2,781
  • 2
  • 12
  • 32
Travis Brown
  • 138,631
  • 12
  • 375
  • 680
  • So, what you are saying is that if I know that something is a Json Object (as opposed to a Json Array) is preferable to implement an OWrites instead of a Writes, right? And regarding to the link in "not personally a fan" I can't find the "more specific return type" anywhere so.. I can't get the idea. Could you please explain a little further on the problem you linked? Thanks for the OWrites explanation :) – vicaba Aug 29 '15 at 22:23
  • 1
    More preferable if your use-case requires it. I don't know if `OWriter` gives you more/different power than plain `Writer` except the fact that you know at compile time it's a JsObject. I think what @Travis meant by saying "more specific return type" is that `OWrites[T] extends Writes[T]` where `Writest[T]` `def writes(in: T): JsValue` whereas the same method in `OWrites[T]` is `def writes(in: T): JsObject` where `JsObject` is subclass of `JsValue`. – goral Aug 30 '15 at 00:56
  • @goral Yeah, I catch the idea in Writes vs OWrites example but what I do not understand is what's happening here: http://stackoverflow.com/questions/31080332/why-is-upcasting-necessary-in-this-scala-code#comment50187425_31080332 – vicaba Aug 30 '15 at 15:14
  • Seems like Travis neither at the time of writing. – goral Aug 30 '15 at 15:45
  • Is there anything other than `implicitly[OWrites[Foo]].writes(foo)` that lets you access an implicit `OWrites`? `Json.toJson(foo)` finds an implicit `Writes`, but if your implicit is an `OWrites`, it treats it as a Writes and takes the output as a JsValue anyway. – emote_control Sep 09 '15 at 18:54