it-swarm.com.de

Geräuschfreies JSON-Format für versiegelte Merkmale mit Play 2.2-Bibliothek

Ich brauche eine einfache JSON-Serialisierungslösung mit minimaler Zeremonie. Ich war also ziemlich glücklich, diese bevorstehende Play 2.2-Bibliothek zu finden. Dies funktioniert perfekt mit einfachen Fallklassen, z.

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]

Folgendes schlägt jedoch fehl:

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

Wie würde ich den vermeintlich fehlenden Extraktor für Foo einrichten?

Oder würden Sie eine andere Standalone-Bibliothek empfehlen, die meinen Fall mehr oder weniger vollautomatisch abwickelt? Es ist mir egal, ob es sich um Makros zur Kompilierzeit oder um Reflexion zur Laufzeit handelt, solange es aus der Box funktioniert.

42
0__

Hier ist eine manuelle Implementierung des Companion-Objekts 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!

Nachprüfung:

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)

Alternativ die direkte Formatdefinition:

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

Jetzt möchte ich idealerweise die Methoden apply und unapply automatisch generieren. Es scheint, dass ich entweder Reflexion verwenden oder in Makros eintauchen muss.

23
0__

AMENDED 2015-09-22

Die Bibliothek play-json-extra enthält die play-json-Varianten - Strategie, aber auch die Strategie [play-json-extensions] (flacher String für Fallobjekte, gemischt mit Objekten für Fallklassen, kein Aufpreis $ Variante oder $ -Typ, sofern nicht erforderlich). Es bietet auch Serialisierer und Deserialisierer für Macramé basierte Enums.

Vorherige Antwort Es gibt jetzt eine Bibliothek namens play-json-variant , in der Sie schreiben können:

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

Dadurch werden die entsprechenden Formate automatisch generiert, und die Disambiguierung des folgenden Falls wird durch Hinzufügen eines $ variant-Attributs (das Äquivalent des class-Attributs von 0__) behandelt.

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

erzeugen würde 

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`
22
Jean

Ein kleiner Fix für die vorherige Antwort von 0__ bezüglich der direkten Formatdefinition - die Reads-Methode hat nicht funktioniert.

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
}
3
ori danus

Play 2.6

Dies kann jetzt elegant mit play-json-derivierten Codecs

Einfach das hinzufügen:

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

Siehe hier für das ganze Beispiel: ScalaFiddle

1
pme