Подтвердить что ты не робот

Беззвучный формат JSON для запечатанных черт с библиотекой Play 2.2

Мне нужно получить простое решение для сериализации JSON с минимальной церемонией. Поэтому я был очень рад найти эту готовую библиотеку Play 2.2. Это отлично работает с классами простых случаев, например

import play.api.libs.json._

sealed trait Foo
case class Bar(i: Int) extends Foo
case class Baz(f: Float) extends Foo

implicit val barFmt = Json.format[Bar]
implicit val bazFmt = Json.format[Baz]

Но выполняется следующее:

implicit val fooFmt = Json.format[Foo]   // "No unapply function found"

Как настроить предполагаемый отсутствующий экстрактор для Foo?

Или вы порекомендовали бы любую другую автономную библиотеку, которая будет обрабатывать мое дело более или менее полностью автоматически? Мне все равно, работает ли это с макросами во время компиляции или отражения во время выполнения, если это работает из коробки.

4b9b3361

Ответ 1

Ниже приведена ручная реализация объекта-компаньона Foo:

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!

Проверка:

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)

Альтернативно, определение прямого формата:

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

В идеале я хотел бы автоматически генерировать методы apply и unapply. Кажется, мне нужно будет использовать отражение или погружение в макросы.

Ответ 2

ПОПРАВКИ 2015-09-22

Библиотека play-json-extra включает play-json-variants, но также и стратегия [play-json-extensions] (плоская строка для объектных объектов, смешанных с объектами для классов case, без дополнительного $variant или $type, если это необходимо). Он также предоставляет сериализаторы и десериализаторы для перечислений macramé.

Предыдущий ответ В настоящее время существует библиотека под названием play-json-variants, которая позволяет вам написать:

implicit val format: Format[Foo] = Variants.format[Foo]

Это автоматически сгенерирует соответствующие форматы, оно также будет обрабатывать неоднозначность следующего случая, добавив атрибут $variant (эквивалент атрибута 0__ class)

sealed trait Foo
case class Bar(x: Int) extends Foo
case class Baz(s: String) extends Foo
case class Bah(s: String) extends Foo

будет генерировать

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`

Ответ 3

Небольшое исправление для предыдущего ответа 0__ в отношении определения прямого формата - метод чтения не работает, и вот мой рефактор к нему, также стал более идиоматичным -

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
}