Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save angeloh/5fa10d8d5321bf650dd9 to your computer and use it in GitHub Desktop.
Save angeloh/5fa10d8d5321bf650dd9 to your computer and use it in GitHub Desktop.
/**
The Play (2.3) json combinator library is arguably the best in the scala world. However it doesnt
work with case classes with greater than 22 fields.
The following gist leverages the shapeless 'Automatic Typeclass Derivation' facility to work around this
limitation. Simply stick it in a common location in your code base, and use like so:
Note: ** Requires Play 2.3 and shapeless 2.1.0
import SWrites._
import SReads._
case class Foo(value: String)
case class Bar(value1: Int, foo: Foo) //Didnt want to type out 23 fields, but you get the idea
implicit val writes: Writes[Foo] = SWrites.deriveInstance
implicit val reads: Reads[Foo] = SReads.deriveInstance
implicit val writes: Writes[Bar] = SWrites.deriveInstance
implicit val reads: Reads[Bar] = SReads.deriveInstance
Additionally, you may get boilerplate free Format typeclasses:
import SFormats._
case class Foo(value: String)
case class Bar(value1: Int, foo: Foo)
def someFunc(value: T)(implicit val format: Format[T]) = ...
**/
import play.api.libs._
import json._
import shapeless.{ `::` => :#:, _ }
import poly._
object SWrites extends LabelledTypeClassCompanion[Writes] {
object typeClass extends LabelledTypeClass[Writes] {
def emptyProduct: Writes[HNil] = Writes(_ => JsNull)
def product[F, T <: HList](name: String, FHead: Writes[F], FTail: Writes[T]) = Writes[F :#: T] {
case head :#: tail =>
val h = FHead.writes(head)
val t = FTail.writes(tail)
(h, t) match {
case (h: JsValue, JsNull) => Json.obj(name -> h)
case (JsNull, t: JsObject) => t
case (h: JsValue, t: JsObject) => Json.obj(name -> h) ++ t
case _ => Json.obj()
}
}
def project[F, G](instance: => Writes[G], to: F => G, from: G => F) = Writes[F]{ f =>
instance.writes(to(f))
}
def emptyCoproduct: Writes[CNil] = Writes(_ => JsNull)
def coproduct[L, R <: Coproduct](name: String, cl: => Writes[L], cr: => Writes[R]) = Writes[L :+: R]{ lr =>
val r = lr match {
case Inl(left) => cl writes left
case Inr(right) => cr writes right
}
r match {
case JsNull => JsString(name)
case o => o
}
}
}
}
object SReads extends LabelledTypeClassCompanion[Reads] {
object typeClass extends LabelledTypeClass[Reads] {
def emptyProduct: Reads[HNil] = Reads(_ => JsSuccess(HNil))
def product[F, T <: HList](name: String, FHead: Reads[F], FTail: Reads[T]) = Reads[F :#: T] {
case obj @ JsObject(fields) =>
for {
head <- FHead.reads(obj \ name)
tail <- FTail.reads(obj - name)
} yield head :: tail
case _ => JsError("Json object required")
}
def project[F, G](instance: => Reads[G], to: F => G, from: G => F) = Reads[F](instance.map(from).reads)
def emptyCoproduct: Reads[CNil] = Reads[CNil](_ => JsError("CNil object not available"))
def coproduct[L, R <: Coproduct](name: String, cl: => Reads[L], cr: => Reads[R]) = Reads[L :+: R]{ js =>
js match {
case js @ JsString(n) if n == name => cl.reads(js).map(Inl.apply)
case js @ _ => cr.reads(js).map(Inr.apply)
}
}
}
}
object SFormats extends LabelledTypeClassCompanion[Format] {
object typeClass extends LabelledTypeClass[Format] {
def emptyProduct: Format[HNil] = Format(
SReads.typeClass.emptyProduct,
SWrites.typeClass.emptyProduct
)
def product[F, T <: HList](name: String, FHead: Format[F], FTail: Format[T]) = Format[F :#: T] (
SReads.typeClass.product[F, T](name, FHead, FTail),
SWrites.typeClass.product[F, T](name, FHead, FTail)
)
def project[F, G](instance: => Format[G], to: F => G, from: G => F) = Format[F](
SReads.typeClass.project(instance, to, from),
SWrites.typeClass.project(instance, to, from)
)
def emptyCoproduct = Format[CNil](
SReads.typeClass.emptyCoproduct,
SWrites.typeClass.emptyCoproduct
)
def coproduct[L, R <: ShapelessCoproduct](name: String, cl: => Format[L], cr: => Format[R]) = Format[L :+: R](
SReads.typeClass.coproduct(name, cl, cr),
SWrites.typeClass.coproduct(name, cl, cr)
)
}
}
@angeloh
Copy link
Author

angeloh commented Apr 19, 2016

If you create a new class with SReads or SWrites, you have to make sure all sub properties also have Reads or Writes available, or it might cause your compile go into an infinite loop.

@aboyett
Copy link

aboyett commented May 13, 2016

@angeloh Would you mind indicating what license you're releasing this code under? Both the Play Framework and Shapeless are Apache 2 licensed, but I don't want to make any assumptions about the restrictions placed on this code before using it.

@nsphung
Copy link

nsphung commented Dec 21, 2016

I'm still having import issue with ShapelessCoproduct with "com.chuusai" %% "shapeless" % "2.1.0". Replacing ShapelessCoproductwith Coproduct seems to work.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment