Json Serialization for Trait with Multiple Case Classes (Sum Types) in Scala's Play Json Serialization for Trait with Multiple Case Classes (Sum Types) in Scala's Play json json

Json Serialization for Trait with Multiple Case Classes (Sum Types) in Scala's Play


I think that is about as simple as you can make it, if you want to avoid writing the code for each explicit subtype maybe you could do it with reflection, use jackson directly or some other json library with reflection support. Or write your own macro to generate the Format from a list of subtypes.


I have a systematic solution for the problem of serializing sum-types in my json pickling library Prickle. Similar ideas could be employed with Play. There is still some config code required, but its high signal/noise, eg final code like:

implicit val fruitPickler = CompositePickler[Fruit].concreteType[Apple].concreteType[Lemon]

CompositePicklers associated with a supertype are configured with one PicklerPair for each known subtype (ie sum type option). The associations are setup at configure time.

During pickling a descriptor is emitted into the json stream describing which subtype the record is.

During unpickling, the descriptor is read out of the json and then used to locate the appropriate Unpickler for the subtype


An example updated for play 2.5:

object TestContact extends App {  sealed trait Shape  object Shape {    val rectFormat = Json.format[Rect]    val circleFormat = Json.format[Circle]    implicit object ShapeFormat extends Format[Shape] {      override def writes(shape: Shape): JsValue = shape match {        case rect: Rect =>          Json.obj("Shape" ->            Json.obj("Rect" ->              Json.toJson(rect)(rectFormat)))        case circle: Circle =>          Json.obj("Shape" ->            Json.obj("Circle" ->              Json.toJson(circle)(circleFormat)))      }      override def reads(json: JsValue): JsResult[Shape] = {        json \ "Shape" \ "Rect" match {          case JsDefined(rectJson) => rectJson.validate[Rect](rectFormat)          case _ => json \ "Shape" \ "Circle" match {            case JsDefined(circleJson) => circleJson.validate[Circle](circleFormat)            case _ => JsError("Not a valide Shape object.")          }        }      }    }  }  case class Rect(width: Double, height: Double) extends Shape  case class Circle(radius: Double) extends Shape  val circle = Circle(2.1)  println(Json.toJson(circle))  val rect = Rect(1.3, 8.9)  println(Json.toJson(rect))  var json = Json.obj("Shape" -> Json.obj("Circle" -> Json.obj("radius" -> 4.13)))  println(json.validate[Shape])  json =    Json.obj("Shape" ->      Json.obj("Rect" ->        Json.obj("width" -> 23.1, "height" -> 34.7)))  println(json.validate[Shape])}