Skip to content

Instantly share code, notes, and snippets.

@jeroenr
Created October 30, 2015 10:44
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jeroenr/47a97849ec1f0c7140ea to your computer and use it in GitHub Desktop.
Save jeroenr/47a97849ec1f0c7140ea to your computer and use it in GitHub Desktop.
import play.api.libs.json._
import play.api.libs.functional.syntax._
import HListUtils._
import JsonFormats._
implicit val specReads = (
(__ \ "id").read[String] and
(__ \ "specs").read((
(__ \ "specA").read[SpecA] and
(__ \ "specB").read[SpecB]
).tupled.hlisted)
).tupled
val json = Json.obj(
"id" -> "foo",
"specs" -> Json.obj(
"specA" -> Json.obj("id" -> "a"),
"specB" -> Json.obj("id" -> "b")
)
)
json.validate(specReads) match {
case JsSuccess((id,specA :: specB :: HNil), _) => println(s"specs: $specA, $specB")
}
import org.specs2.mutable.Specification
import play.api.libs.functional.Applicative
import play.api.libs.json._
import shapeless._
import HList._
import Tuples._
import play.api.libs.functional.syntax._
object HListUtils {
private def toHList[H : Reads, T <: HList : Reads](l: List[JsValue])(implicit applicative: Applicative[JsResult]): JsResult[H :: T] = l match {
case scala.::(head, tail) =>
applicative.apply(
//JsResult[T => H :: T]
applicative.map(
implicitly[Reads[H]].reads(head),
(h: H) => (t: T) => h :: t
),
//JsResult[T]
implicitly[Reads[T]].reads(JsArray(tail))
)
case _ => JsError("can't convert empty list using multi-element HList")
}
implicit def HNilReads = Reads[HNil]{ js => js match {
case JsArray(values) if(values.isEmpty) => JsSuccess(HNil)
case JsObject(values) if(values.isEmpty) => JsSuccess(HNil)
case _ => JsError("Not empty JsArray or JsObject")
} }
implicit def hlistReads[H : Reads, T <: HList : Reads](implicit applicative: Applicative[JsResult]) = Reads[H :: T]{
case arr: JsArray => toHList[H, T](arr.value.toList)
case obj: JsObject => toHList[H, T](obj.values.toList)
case js => toHList[H, T](List(js))
//JsError("Single JsValue can't be mapped to multi-element HList")
}
implicit def HNilWrites = Writes[HNil]{ hl => JsArray() }
/*implicit def hlistHNilWrites[H : Writes] = Writes[H :: HNil]{ hl =>
val head :: HNil = hl
JsArray(Seq(implicitly[Writes[H]].writes(head)))
}*/
implicit def hlistWrites[H : Writes, T <: HList : Writes] = Writes[H :: T]{ case hl =>
implicitly[Writes[H]].writes(hl.head) +: implicitly[Writes[T]].writes(hl.tail).as[JsArray]
}
implicit class TupleReadsOps[ P <: Product ](r: Reads[P]) {
def hlisted(implicit hlister : HLister[P]) = r map { _.hlisted }
}
implicit class TupleWritesOps[ P <: Product ](w: Writes[P]) {
def contramap[A, B](wa:Writes[A], f: B => A): Writes[B] = Writes[B]( b => wa.writes(f(b)) )
def hlisted[T <: HList](implicit hlister : HLister[P], tupler: TuplerAux[T, P]) = contramap(w, (hl: T) => hl.tupled)
}
def JsArrayIso[L <: HList](implicit r: Reads[L], w: Writes[L]) = new Iso[JsArray, L] {
def to(t: JsArray): L = r.reads(t).get
def from(l: L): JsArray = w.writes(l).asInstanceOf[JsArray]
}
}
trait Spec
case class SpecA(id: String) extends Spec
case class SpecB(id: String) extends Spec
object JsonFormats {
implicit val specAFormat = Json.format[SpecA]
implicit val specBFormat = Json.format[SpecB]
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment