-
-
Save angeloh/5fa10d8d5321bf650dd9 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
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 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.
I'm still having import issue with ShapelessCoproduct
with "com.chuusai" %% "shapeless" % "2.1.0"
. Replacing ShapelessCoproduct
with Coproduct
seems to work.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.