Skip to content

Instantly share code, notes, and snippets.

@mandubian
Created February 12, 2014 13:07
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 mandubian/8955241 to your computer and use it in GitHub Desktop.
Save mandubian/8955241 to your computer and use it in GitHub Desktop.
Reads/Writes for a simple AST
import play.api.libs.json._
import play.api.libs.functional.syntax._
sealed trait Parent {
val typ: String
}
case class Foo(blabla: Long, override val typ: String = "foo") extends Parent
object Foo {
// NOT IMPLICIT because Writes is contravariant so Writes[Parent] would collide with this one
val writer = Json.writes[Foo]
// CAN BE IMPLICIT (Reads is invariant)
implicit val reader = Json.reads[Foo]
}
case class Bar(blibli: String, override val typ: String = "bar") extends Parent
object Bar {
// NOT IMPLICIT because Writes is contravariant so Writes[Parent] would collide with this one
val writer = Json.writes[Bar]
// CAN BE IMPLICIT (Reads is invariant)
implicit val reader = Json.reads[Bar]
}
object Parent {
implicit val writer = Writes[Parent] {
case foo: Foo => Json.toJson(foo)(Foo.writer)
case bar: Bar => Json.toJson(bar)(Bar.writer)
}
// for reads, if you want to use the discriminator, you need to trick a bit & "cast" to Parent as Reads is invariant
implicit val reader = (__.read[JsValue] and (__ \ "typ").read[String]).tupled.flatMap {
case (js, "foo") => Reads{ _ => Json.fromJson[Foo](js).map(f => f:Parent) }
case (js, "bar") => Reads{ _ => Json.fromJson[Bar](js).map(f => f:Parent) }
case _ => Reads[Parent] { _ => JsError("expected either foo or bar") }
}
}
scala> val bar : Parent = Bar("tata")
bar: Parent = Bar(tata,bar)
scala> val foo : Parent = Foo(5L)
foo: Parent = Foo(5,foo)
scala> Json.toJson(foo)
res0: play.api.libs.json.JsValue = {"blabla":5,"typ":"foo"}
scala> Json.toJson(bar)
res1: play.api.libs.json.JsValue = {"blibli":"tata","typ":"bar"}
scala> Json.fromJson[Parent](Json.parse("""{"blibli":"tata","typ":"bar"}"""))
res6: play.api.libs.json.JsResult[Parent] = JsSuccess(Bar(tata,bar),)
scala> Json.fromJson[Parent](Json.parse("""{"blibli":"tata","typ":"foo"}"""))
res7: play.api.libs.json.JsResult[Parent] = JsError(List((/blabla,List(ValidationError(error.path.missing,WrappedArray())))))
scala> Json.fromJson[Parent](Json.parse("""{"blabla":"tata","typ":"foo"}"""))
res8: play.api.libs.json.JsResult[Parent] = JsError(List((/blabla,List(ValidationError(error.expected.jsnumber,WrappedArray())))))
scala> Json.fromJson[Parent](Json.parse("""{"blabla":5,"typ":"foo"}"""))
res9: play.api.libs.json.JsResult[Parent] = JsSuccess(Foo(5,foo),)
scala> Json.fromJson[Parent](Json.parse("""{"blabla":5,"typ":"foo2"}"""))
res0: play.api.libs.json.JsResult[Parent] = JsError(List((,List(ValidationError(expected either foo or bar,WrappedArray())))))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment