Skip to content

Instantly share code, notes, and snippets.

@steinybot
Last active March 7, 2018 04:42
Show Gist options
  • Save steinybot/85b42fc375d3767ab5ed0dbe41bcaa23 to your computer and use it in GitHub Desktop.
Save steinybot/85b42fc375d3767ab5ed0dbe41bcaa23 to your computer and use it in GitHub Desktop.
Play Json Reads and Writes for Coproducts
package com.geneious.nucleus.service.util.play.json
import play.api.libs.json._
import shapeless._
trait CoproductFormats {
implicit val cNilReads: Reads[CNil] = Reads(_ => JsError())
implicit val cNilWrites: Writes[CNil] = Writes(_ => JsNull)
implicit val cNilOWrites: OWrites[CNil] = OWrites(_ => JsObject.empty)
final class CoproductReads[HT] {
def apply[H: Reads, T <: Coproduct : Reads]()(implicit evidence: (H :+: T) =:= HT): Reads[HT] = {
implicitCoproductReads[H, T].map(evidence)
}
}
def coproductReads[HT]: CoproductReads[HT] = new CoproductReads[HT]
implicit def implicitCoproductReads[H: Reads, T <: Coproduct : Reads]: Reads[H :+: T] = {
val hReads = implicitly[Reads[H]].map[H :+: T](Inl[H, T])
val tReads = implicitly[Reads[T]].map[H :+: T](Inr[H, T])
JsonReads.firstReads(hReads, tReads)
}
final class CoproductWrites[HT] {
def apply[H: Writes, T <: Coproduct : Writes]()(implicit evidence: HT =:= (H :+: T)): Writes[HT] = {
Writes[HT] {
ht => implicitCoproductWrites[H, T].writes(evidence(ht))
}
}
}
def coproductWrites[HT]: CoproductWrites[HT] = new CoproductWrites[HT]
def implicitCoproductWrites[H: Writes, T <: Coproduct : Writes]: Writes[H :+: T] = {
Writes[H :+: T] {
case Inl(head) => implicitly[Writes[H]].writes(head)
case Inr(tail) => implicitly[Writes[T]].writes(tail)
}
}
final class CoproductOWrites[HT] {
def apply[H: OWrites, T <: Coproduct : OWrites]()(implicit evidence: HT =:= (H :+: T)): OWrites[HT] = {
OWrites[HT] { ht =>
implicitCoproductOWrites[H, T].writes(evidence(ht))
}
}
}
def coproductOWrites[HT]: CoproductOWrites[HT] = new CoproductOWrites[HT]
def implicitCoproductOWrites[H: OWrites, T <: Coproduct : OWrites]: OWrites[H :+: T] = {
OWrites[H :+: T] {
case Inl(head) => implicitly[OWrites[H]].writes(head)
case Inr(tail) => implicitly[OWrites[T]].writes(tail)
}
}
}
object CoproductFormats extends CoproductFormats
import play.api.libs.json._
import scala.annotation.tailrec
object JsonReads {
def firstReads[A](read: Reads[_ <: A], reads: Reads[_ <: A]*): Reads[A] = {
@tailrec
def loop(json: JsValue, remaining: Seq[Reads[_ <: A]], error: JsError): JsResult[A] = remaining match {
case head +: tail => head.reads(json) match {
case headError: JsError => loop(json, tail, JsError.merge(error, headError))
case success: JsSuccess[A] => success
}
case Seq() => error
}
Reads(loop(_, read +: reads, JsError()))
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment