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

I often have to serialize/deserialize sum types (like Either[S,T]), and I haven't yet found a general or elegant way to do it. Here's an example type (essentially equivalent to Either)

sealed trait OutcomeType
case class NumericOutcome(units: String)              extends OutcomeType
case class QualitativeOutcome(outcomes: List[String]) extends OutcomeType

Here's my best effort at a companion object that implements serialization. It works, but it's very tiresome to write these sorts of things over and over for every sum type. Are there any suggestions for making it nicer and/or more general?

import play.api.libs.json._
import play.api.libs.functional.syntax._

object OutcomeType {

  val fmtNumeric     = Json.format[NumericOutcome]
  val fmtQualitative = Json.format[QualitativeOutcome]

  implicit object FormatOutcomeType extends Format[OutcomeType] {
    def writes(o: OutcomeType) = o match {
      case n@NumericOutcome(_)     => Json.obj("NumericOutcome"     -> Json.toJson(n)(fmtNumeric))
      case q@QualitativeOutcome(_) => Json.obj("QualitativeOutcome" -> Json.toJson(q)(fmtQualitative))
    }

    def reads(json: JsValue) = (
      Json.fromJson(json \ "NumericOutcome")(fmtNumeric) orElse
      Json.fromJson(json \ "QualitativeOutcome")(fmtQualitative)
    )
  }
}

Answers


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])
}

Need Your Help

How to keep track of pages visited by the user

c# asp.net .net vb.net iis

I am working with a .Net(VB.Net) project. I need to keep track of the pages that my users keep visiting.

Menu displaying in Chrome but not Safari

html css html5 css3

So I am building out a website for a client, and I've come across an interesting bug. The top nav appears in every browser but Safari. The thing is, the links are there, they just are not visible. ...