19

This creates a Writes for a case class

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

case class A(a: String, b: String, c: String)
(JsPath.write[String] and
    JsPath.write[String] and
    JsPath.write[String])(unlift(A.unapply))

This can be extended to work for 2, 3, 4, 5, 6, etc. parameters...but not 1.

case class B(a: String)
(JsPath.write[String])(unlift(B.unapply))

Compiler error:

error: overloaded method value write with alternatives:
  (t: String)(implicit w:  play.api.libs.json.Writes[String])play.api.libs.json.OWrites[play.api.libs.json.JsValue] <and>
  (implicit w: play.api.libs.json.Writes[String])play.api.libs.json.OWrites[String]
  cannot be applied to (B => String)
              (JsPath.write[String])(unlift(B.unapply))
                           ^

A similar problem happens for Reads.

How can I get Reads and Writes for single-parameter case clases?

Paul Draper
  • 78,542
  • 46
  • 206
  • 285
  • 2
    See my answer [here](http://stackoverflow.com/a/26458085/334519)—you want plain old `contramap`, since the applicative builder syntax doesn't work with a single element. – Travis Brown Dec 15 '14 at 06:24

2 Answers2

16

Like Travis said:

  1. Transforming an existing Reads: use the map method
  2. Transforming an existing Writes: use contramap

However, contramap only works on Writes that produce JsObject. Your writes will fail at runtime:

val w = JsPath.write[String].contramap[B](_.a)
scala> w.writes(B("Hello"))
java.lang.RuntimeException: when empty JsPath, expecting JsObject

You can create a Writes "from scratch" using Writes.apply:

Writes[B](b => JsString(b.a))

Similarly you can create a Reads using Reads.apply.

Dimitri
  • 1,786
  • 14
  • 22
  • 1
    Thanks. I knew I could always use `Reads.apply` and `Writes.apply`...it just looked odd in my code. "Here's a case class, and another one, and...oh...that looks different." Thanks for help. – Paul Draper Dec 15 '14 at 10:28
-2

implicit val reads: Reads[A] = (JsPath \ "ax").read[B].map(A.apply)