Skip to content

Instantly share code, notes, and snippets.

@negator
Last active January 15, 2019 13:34
Show Gist options
  • Save negator/5139ddb5f6d91cbe7b0c to your computer and use it in GitHub Desktop.
Save negator/5139ddb5f6d91cbe7b0c 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.0.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.auto.derive[Foo]
implicit val reads: Reads[Foo] = SReads.auto.derive[Foo]
implicit val writes: Writes[Bar] = SWrites.auto.derive[Bar]
implicit val reads: Reads[Bar] = SReads.auto.derive[Bar]
Additionally, you may get boilerplate free Format typeclasses:
import SFormats.auto._
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._
implicit val writesInstance: LabelledProductTypeClass[Writes] = new LabelledProductTypeClass[Writes] {
def emptyProduct: Writes[HNil] = Writes(_ => Json.obj())
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 (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)))
}
object SWrites extends LabelledProductTypeClassCompanion[Writes]
implicit val readsInstance: LabelledProductTypeClass[Reads] = new LabelledProductTypeClass[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)
}
object SReads extends LabelledProductTypeClassCompanion[Reads]
implicit val formatInstance: LabelledProductTypeClass[Format] = new LabelledProductTypeClass[Format] {
def emptyProduct: Format[HNil] = Format(
readsInstance.emptyProduct,
writesInstance.emptyProduct
)
def product[F, T <: HList](name: String, FHead: Format[F], FTail: Format[T]) = Format[F :#: T] (
readsInstance.product[F, T](name, FHead, FTail),
writesInstance.product[F, T](name, FHead, FTail)
)
def project[F, G](instance: => Format[G], to: F => G, from: G => F) = Format[F](
readsInstance.project(instance, to, from),
writesInstance.project(instance, to, from)
)
}
object SFormats extends LabelledProductTypeClassCompanion[Format]
@aboyett
Copy link

aboyett commented May 13, 2016

@negator 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.

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