framework - Formato JSON sin ruido para rasgos sellados con la biblioteca Play 2.2
play json github (3)
Aquí hay una implementación manual del objeto complementario 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!
Verificación:
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)
Alternativamente, la definición de formato directo:
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))
}
}
Ahora, idealmente, me gustaría generar automáticamente los métodos de apply
y unapply
. Parece que necesitaré usar reflexión o inmersión en macros.
Necesito obtener una solución de serialización JSON simple con la ceremonia mínima. Así que me alegré mucho al encontrar esta próxima biblioteca de Play 2.2 . Esto funciona perfectamente con clases de casos simples, por ejemplo
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]
Pero el siguiente falla:
implicit val fooFmt = Json.format[Foo] // "No unapply function found"
¿Cómo instalaría el presunto extractor extraído para Foo
?
¿O recomendaría alguna otra biblioteca independiente que maneje mi caso de forma más o menos completamente automática? No me importa si eso es con macros en tiempo de compilación o reflexión en tiempo de ejecución, siempre y cuando salga de la caja.
Una pequeña solución para la respuesta anterior en 0__ con respecto a la definición de formato directo - el método de lectura no funcionó, y aquí está mi refactorio, para volverse más idiomático -
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
}
ENMENDADO 2015-09-22
La biblioteca play-json-extra incluye la estrategia play-json-variants , pero también la estrategia [play-json-extensions] (cadena plana para objetos de mayúsculas y minúsculas mezclados con objetos para clases de casos sin variante $ extra o $ type a menos que sea necesario). También proporciona serializadores y deserializadores para enums basados en macramé .
Respuesta anterior Ahora hay una biblioteca llamada play-json-variants que te permite escribir:
implicit val format: Format[Foo] = Variants.format[Foo]
Esto generará automáticamente los formatos correspondientes, también manejará la desambiguación del caso siguiente al agregar un atributo $ variant (el equivalente del atributo de class
de 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
generaría
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`