最小限の式でシンプルな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
の不足しているとされるエクストラクターをどのように設定しますか?
または、私のケースを多かれ少なかれ完全に自動的に処理する他のスタンドアロンライブラリをお勧めしますか?それがそのまま動作する限り、コンパイル時のマクロと実行時のリフレクションのどちらが問題になるかは気にしません。
次に、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
メソッドを自動的に生成したいと思います。リフレクションを使用するか、マクロに飛び込む必要があるようです。
2015年9月22日改訂
ライブラリ play-json-extra には play-json-variants 戦略が含まれますが、[play-json-extensions]戦略も含まれます(オブジェクトと混合されたケースオブジェクトのフラット文字列)ケースクラスの場合、必要がない限り、追加の$ 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`
Play 2.7
sealed traits
はplay-json
でサポートされています。
object Foo{
implicit val format = Json.format[Foo]
}
2.6をプレイする
これは play-json-derived-codecs を使用してエレガントに行うことができます
これを追加するだけです:
object Foo{
implicit val jsonFormat: OFormat[Foo] = derived.oformat[Foo]()
}
例全体については、こちらをご覧ください: ScalaFiddle
直接フォーマットの定義に関する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
}