Бесшумный формат 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?

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


person 0__    schedule 10.06.2013    source источник
comment
Не хватает какого-то кода? Это единственное, что определяет Foo линию sealed trait Foo? Что же тогда вы ожидаете? Я полагаю, что Json.format будет работать для обычных классов, если у них есть методы apply() и unapply().   -  person Carsten    schedule 10.06.2013
comment
Играть json, а также поднимать json должно быть нормально. Видите ли, вы пытаетесь получить формат для признака, но почти все библиотеки, обеспечивающие прозрачную сериализацию, основаны на классах case. Просто используйте классы вариантов и сопоставление с образцом, и все будет в порядке.   -  person vitalii    schedule 10.06.2013
comment
Мне нужно иметь возможность сериализовать классы типов. Поэтому мне нужен формат для закрытого признака, который расширяется рядом классов case. Должен быть довольно распространенный сценарий.   -  person 0__    schedule 10.06.2013
comment
Автоматический Json.format для трейтов невозможен, но вы можете написать их: stackoverflow.com/questions/14145432/; также я наткнулся на этот вопрос, который может вас заинтересовать: stackoverflow.com/questions/6891393/   -  person Carsten    schedule 10.06.2013
comment
Недавно я написал макрос JSON, который генерирует код Джексона для любой структуры объекта, используя информацию о типе времени компиляции. Макрос может сгенерировать оператор сопоставления для всех подтипов запечатанного типа через API отражения knownDirectSubclasses, показанный здесь: scala-lang.org/api/current/. Я пока не знаю ни одной другой библиотеки Json, которая бы это сделала ...   -  person Andy    schedule 14.06.2013
comment
@ Энди, не могли бы вы поделиться этим кодом?   -  person 0__    schedule 17.06.2013
comment
Если вы можете подождать неделю, я могу разместить его на GitHub. Предупреждение: он был написан для нашего случая использования на работе, а не как универсальная библиотека.   -  person Andy    schedule 17.06.2013
comment
@ Энди Нет проблем. Я наполовину написал свою реализацию прямо сейчас. writer уже работает, но некоторые проблемы нужно исправить с одноэлементными объектами в части reader.   -  person 0__    schedule 17.06.2013
comment
@ 0__, в зависимости от ваших целей, возможно, мы сможем объединить силы   -  person Andy    schedule 17.06.2013
comment
Да, конечно, это проект - действительно, я думаю, что это должно быть вытащил в play-json.   -  person 0__    schedule 17.06.2013


Ответы (4)


ИЗМЕНЕНО 22 сентября 2015 г.

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

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

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

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

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`
person Jean    schedule 16.12.2013
comment
Спасибо за новый ответ. Я не понимаю, почему автор в основном переписал то, что я сделал, но что ж ... Мы у вас такая же проблема с knownDirectSubclasses, не предоставленным безопасным образом макросистемой (и подтверждение того, что это не будет исправлено в ближайшее время) - person 0__; 16.12.2013
comment
Скорее всего, он об этом не знал ... как и я :) - person Jean; 16.12.2013
comment
вы бы не знали о библиотеке, которая создает форматы со значениями по умолчанию для отсутствующих свойств (см. stackoverflow.com/questions/20616677/ для подробностей) - person Jean; 17.12.2013
comment
вы можете поместить это как запрос функции в моем проекте. Я бы не хотел генерировать значения по умолчанию всегда, но я мог бы представить, что это будет вариант, например AutoFormat[Foo](defaults = true) - person 0__; 17.12.2013
comment
Я бы не хотел постоянно генерировать значения по умолчанию. В идеале должно быть 2 сигнатуры: 1 для установки по умолчанию всех значений, которые могут быть заданы по умолчанию, и withDefault (ключ, значение), что второй будет гарантировать, что имя ключа существует и предоставленное значение по умолчанию имеет правильный тип. Я начал писать запрос функции, когда понял, что мне это нужно для обычных классов case, а не только для производных от запечатанных черт ... - person Jean; 17.12.2013
comment
knownDirectSubclasses не работает, см. 16) @ docs.scala-lang.org/overviews/macros/ - person Stefan K.; 25.02.2015
comment
Ссылка play-json-extra не работает. - person Brian McCutchon; 25.05.2017
comment
@BrianMcCutchon спасибо за отчет, исправил ссылку. К сожалению, отображаемый сайт не работает (сброшен на целевую страницу apache), но документ доступен внутри репо. - person Jean; 26.05.2017

Вот ручная реализация сопутствующего объекта 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. Похоже, мне нужно будет либо использовать отражение, либо погрузиться в макросы.

person 0__    schedule 17.06.2013
comment
на мой взгляд, подход «применить / отменить» довольно опасен. Если имя класса json не исчерпано (Malformed json), вызов get сработает, и у вас не будет записи об этом в журнал ошибок json. - person Felix; 27.06.2016
comment
Это та же проблема, что и у меня ... однако пример кода в ответе мне не подходит ... это все еще предпочтительный подход? - person Matt; 27.08.2018
comment
для меня это выдает ошибку в этой строке case "Bar" => Json.fromJson[Bar](data)(barFmt) из-за несоответствия с Actual: JsValue [Bar] и Expected: JsValue [Foo] - person Arun Gupta; 31.10.2018

Небольшое исправление для предыдущего ответа от 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
}
person ori danus    schedule 20.10.2017

Play 2.7

sealed traits поддерживаются в play-json.

object Foo{
  implicit val format = Json.format[Foo]
}

Играть в 2.6

Теперь это можно сделать элегантно с помощью play-json-производных-кодеков.

Просто добавьте это:

object Foo{
    implicit val jsonFormat: OFormat[Foo] = derived.oformat[Foo]()
}

См. Здесь полный пример: ScalaFiddle

person pme    schedule 18.09.2018