score:25

Accepted answer

here is a manual implementation of the foo companion object:

implicit val barfmt = json.format[bar]
implicit val bazfmt = json.format[baz]

object foo {
  def unapply(foo: foo): option[(string, jsvalue)] = {
    val (prod: product, sub) = foo match {
      case b: bar => (b, json.tojson(b)(barfmt))
      case b: baz => (b, json.tojson(b)(bazfmt))
    }
    some(prod.productprefix -> sub)
  }

  def apply(`class`: string, data: jsvalue): foo = {
    (`class` match {
      case "bar" => json.fromjson[bar](data)(barfmt)
      case "baz" => json.fromjson[baz](data)(bazfmt)
    }).get
  }
}
sealed trait foo
case class bar(i: int  ) extends foo
case class baz(f: float) extends foo

implicit val foofmt = json.format[foo]   // ça marche!

verification:

val in: foo = bar(33)
val js  = json.tojson(in)
println(json.prettyprint(js))

val out = json.fromjson[foo](js).getorelse(sys.error("oh no!"))
assert(in == out)

alternatively the direct format definition:

implicit val foofmt: format[foo] = new format[foo] {
  def reads(json: jsvalue): jsresult[foo] = json match {
    case jsobject(seq(("class", jsstring(name)), ("data", data))) =>
      name match {
        case "bar"  => json.fromjson[bar](data)(barfmt)
        case "baz"  => json.fromjson[baz](data)(bazfmt)
        case _      => jserror(s"unknown class '$name'")
      }

    case _ => jserror(s"unexpected json value $json")
  }

  def writes(foo: foo): jsvalue = {
    val (prod: product, sub) = foo match {
      case b: bar => (b, json.tojson(b)(barfmt))
      case b: baz => (b, json.tojson(b)(bazfmt))
    }
    jsobject(seq("class" -> jsstring(prod.productprefix), "data" -> sub))
  }
}

now ideally i would like to automatically generate the apply and unapply methods. it seems i will need to use either reflection or dive into macros.

score:4

a small fix for the previous answer by 0__ regarding the direct format definition - the reads method didn't work, and here is my refactor to it, to also become more idiomatic -

def reads(json: jsvalue): jsresult[foo] = {

  def from(name: string, data: jsobject): jsresult[foo] = name match {
    case "bar"  => json.fromjson[bar](data)(barfmt)
    case "baz"  => json.fromjson[baz](data)(bazfmt)
    case _ => jserror(s"unknown class '$name'")
  }

  for {
    name <- (json \ "class").validate[string]
    data <- (json \ "data").validate[jsobject]
    result <- from(name, data)
  } yield result
}

score:6

play 2.7

sealed traits are supported in play-json.

object foo{
  implicit val format = json.format[foo]
}

play 2.6

this can be done now elegantly with play-json-derived-codecs

just add this:

object foo{
    implicit val jsonformat: oformat[foo] = derived.oformat[foo]()
}

see here for the whole example: scalafiddle

score:26

amended 2015-09-22

the library play-json-extra includes the play-json-variants strategy, but also the [play-json-extensions] strategy (flat string for case objects mixed with objects for case classes no extra $variant or $type unless needed). it also provides serializers and deserializers for macramé based enums.

previous answer there is now a library called play-json-variants which allows you to write :

implicit val format: format[foo] = variants.format[foo]

this will generate the corresponding formats automatically, it will also handle disambiguation of the following case by adding a $variant attribute (the equivalent of 0__ 's class attribute)

sealed trait foo
case class bar(x: int) extends foo
case class baz(s: string) extends foo
case class bah(s: string) extends foo

would generate

val bahjson = json.obj("s" -> "hello", "$variant" -> "bah") // this is a `bah`
val bazjson = json.obj("s" -> "bye", "$variant" -> "baz") // this is a `baz`
val barjson = json.obj("x" -> "42", "$variant" -> "bar") // and this is a `bar`

Related Query

More Query from same tag