Search code examples
scalaplay-json

Flatten Writes with Play Json


Due to the 22 field limit I had to split a large case class into smaller classes. How can I flatten the Writes of this large class?

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

case class B(x: Option[Int], y: Option[Int])

object B {
  implicit val format: (Reads[B], Writes[B]) => Format[B] = Format[B]
}

case class C(z: Option[Int], w: Option[Int])

object C {
  implicit val format: (Reads[C], Writes[C]) => Format[C] = Format[C]
}

case class A(b: B, c: C)

object A {
  implicit val reads: Reads[A] =
    (Reads.of[B] and Reads.of[C]) (A.apply _)

  implicit val writes: Writes[A] = ???
  /*
  val a = A(B(1, 2), C(3, 4)

  Json.toJson(a) should be

  {
    "x": 1
    "y": 2
    "z": 3
    "w": 4
  }
   */
}

Solution

  • Short story, you can do this :

    implicit val writes: Writes[A] = JsPath.write[B].and(JsPath.write[C]) (unlift(A.unapply _))
    

    Long story: Why are you able to use and method ?

    and is available for any class that implements the typeclass FunctionalCanBuild. What I noticed so far is that there is no such typeclass for Writes, however there is one for OWrites.

    Using JsPath.write[B] yields a OWrites value, therefore and method is available. On the other hand, using Writes.of[] yields a Writes, and OWrites.of[] yields a Writes[]. In the end, any method to get a OWrites[] will allow you to use the key word and.

    Also, looking further into the code, any instance of Applicative typeclass is translatable to an instance of FunctionalCanBuild. This typeclass is implemented for any Reads hence the available and method on the Readers.