I've got some JSON that I need to parse, and my project already uses Play, so that seems like the right place to start.
Given these definitions:
sealed trait Thing
case class Foo(i: Int) extends Thing
case class Bar(s: String, t: List[Thing]) extends Thing
I would want this JSON:
{
s: "doomy doomy doom",
t: [ 24, { s: "doooom!", t: [ 1, 2, 3 ] }, 42, 126 ]
}
To become this object:
Bar("doomy doomy doom", List(Foo(24), Bar("doooom!", List(Foo(1), Foo(2), Foo(3))), Foo(42), Foo(126)))
Any suggestions?
To add to @dmytro-mitin answer, you can use Scala's value class for Foo
. Play JSON documentation includes Reads/Writes/Formats for value classes. Then you can use Int
instead of an object with single field in your original example. Here is an updated example with a value class:
import play.api.libs.json._
import play.api.libs.functional.syntax._
sealed trait Thing extends Any
object Thing {
implicit val thingReads: Reads[Thing] = Foo.fooReads.or[Thing](Bar.barReads.widen)
implicit val thingWrites: Writes[Thing] = {
case f: Foo => Foo.fooWrites.writes(f)
case b: Bar => Bar.barWrites.writes(b)
}
}
case class Foo(i: Int) extends AnyVal with Thing
object Foo {
implicit val fooReads: Reads[Foo] = Json.valueReads[Foo]
implicit val fooWrites: Writes[Foo] = Json.valueWrites[Foo]
}
case class Bar(s: String, t: List[Thing]) extends Thing
object Bar {
implicit val barReads: Reads[Bar] = (
(__ \ "s").read[String] and
(__ \ "t").lazyRead(Reads.list[Thing](Thing.thingReads))
)(Bar.apply _)
implicit val barWrites: Writes[Bar] = (
(__ \ "s").write[String] and
(__ \ "t").lazyWrite(Writes.list[Thing](Thing.thingWrites))
)(unlift(Bar.unapply))
}
val thing: Thing = Bar("doom", List(Foo(12), Bar("doom", List(Foo(1), Foo(2), Foo(3)))))
val json = Json.toJson(thing)
val str = Json.stringify(json)
// {"s":"doom","t":[12,{"s":"doom","t":[1,2,3]}]}
val thing1 = Json.parse(str).as[Thing]
// Bar(doom,List(Foo(12), Bar(doom,List(Foo(1), Foo(2), Foo(3)))))
thing1 == thing // true