How to write a Play JSON writes converter for a ca

2020-02-23 09:24发布

问题:

In Play 2.3, I have a case class with a single optional double member:

case class SomeClass(foo: Option[Double])

I need a JSON write converter that handles the member as nullable:

implicit val someClassWrite: Writes[SomeClass] = ???

The Play docs provide an example:

case class DisplayName(name:String)
implicit val displayNameWrite: Writes[DisplayName] = Writes {
  (displayName: DisplayName) => JsString(displayName.name)
}

But sadly I can't figure out how to do this for 1) a single nullable and 2) a double. Any ideas? Thanks.

Update #1: The only solution I can come up with is this:

implicit val someClassWrite: Writes[SomeClass] = Writes {
  (someClass: SomeClass) => someClass.foo match {
    case Some(f) => JsNumber(BigDecimal(f))
    case _ => JsNull
}

Update #2: Ignore my solution. Travis Brown's is the one.

回答1:

Writes isn't a covariant functor, so you can't use map, but you can use contramap:

import play.api.libs.json._
import play.api.libs.functional.syntax._

implicit val someClassWrites: Writes[SomeClass] =
  (__ \ 'foo).writeNullable[Double].contramap(_.foo)

If you have more than one member, you can use Play's FunctionalBuilder syntax:

case class AnotherClass(foo: Option[Double], bar: Option[String])

implicit val anotherClassWrites: Writes[AnotherClass] = (
  (__ \ 'foo).writeNullable[Double] and
  (__ \ 'bar).writeNullable[String]
)(ac => (ac.foo, ac.bar))

In the first case the argument to contramap is just a function from the type you want a Writes for to the type in the Writes you're calling contramap on. In the second case, the function at the end is from the target (AnotherClass here) to a tuple of the Writes instances you've built up with and (in this case Option[Double] and Option[String]).



回答2:

The easy way is:

import play.api.libs.json.Json

implicit val fmt = Json.format[SomeClass]

Which uses a macro to auto-generate the json format for you. Beats implementing writes directly.