web-dev-qa-db-ja.com

Scala Play Framework2.1のJSONへ

2.1RC PlayFrameworkでScalaをJSONに変換しようとしています。

私は次のことをしてJSONを取得できます:

import play.api.libs.json._

val a1=Map("val1"->"a", "val2"->"b")
Json.toJSon(a1)

A1はMap [String、String]であり、問​​題なく機能するためです。

しかし、Map [String、Object]があるようなもっと複雑なものがある場合、それは機能しません。

val a = Map("val1" -> "xxx", "val2"-> List("a", "b", "c"))
Json.toJSon(a1)
>>> error: No Json deserializer found for type scala.collection.immutable.Map[String,Object]

私は次のようなことができることがわかりました:

val a2 = Map("val1" -> Json.toJson("a"), "val2" -> Json.toJson(List("a", "b", "c")))
Json.toJson(a2)

そしてそれはうまくいきます。

しかし、どうすれば一般的な方法でそれを行うことができますか?私は次のようなことができると思いました:

a.map{ case(k,v)=> (k, Json.toJson(v) )}
>>> error: No Json deserializer found for type Object

しかし、それでも逆シリアル化できないというエラーが発生します


追加情報:

Json.toJsonは、Map [String、String]をJsValueに変換できます。

scala> val b = Map( "1" -> "A", "2" -> "B", "3" -> "C", "4" -> "D" )
b: scala.collection.immutable.Map[String,String] = Map(1 -> A, 2 -> B, 3 -> C, 4 -> D)

scala> Json.toJson(b)
res31: play.api.libs.json.JsValue = {"1":"A","2":"B","3":"C","4":"D"}

しかし、Map [String、Object]の変換に失敗します。

scala> a
res34: scala.collection.immutable.Map[String,Object] = Map(val1 -> xxx, val2 -> List(a, b, c))

scala> Json.toJson(a)
<console>:12: error: No Json deserializer found for type scala.collection.immutable.Map[String,Object]. Try to implement an implicit Writes or Format for this type.
          Json.toJson(a)

ScalaをJsonに変換する際に、このPlay Frameworkページの「ヒント」を使用すると、次のことがわかりました( http://www.playframework.org/documentation/2.0.1/ ScalaJson ):

Map [String、Object]の代わりにMap [String、JsValue]がある場合、Json.toJson()は機能します。

scala> val c = Map("aa" -> Json.toJson("xxxx"), "bb" -> Json.toJson( List("11", "22", "33") ) )
c: scala.collection.immutable.Map[String,play.api.libs.json.JsValue] = Map(aa -> "xxxx", bb -> ["11","22","33"])

scala> Json.toJson(c)
res36: play.api.libs.json.JsValue = {"aa":"xxxx","bb":["11","22","33"]}

だから、私が欲しいのは、Map [String、Object]が与えられ、Objectの値がすべて元々String型またはList [String]型であったことを知っている場合、関数Json.toJson()をすべてに適用する方法ですマップ内の値を取得し、Map [String、JsValue]を取得します。

また、純粋に文字列である値と、List [String]型である(だった)値を除外できることもわかりました。

scala> val a1 = a.filter({case(k,v) => v.isInstanceOf[String]})
a1: scala.collection.immutable.Map[String,Object] = Map(val1 -> xxx)

scala> val a2 = a.filter({case(k,v) => v.isInstanceOf[List[String]]})
<console>:11: warning: non-variable type argument String in type List[String] is unchecked since it is eliminated by erasure
   val a2 = a.filter({case(k,v) => v.isInstanceOf[List[String]]})
                                                 ^
a2: scala.collection.immutable.Map[String,Object] = Map(val2 -> List(a, b, c))

List [String]フィルタリングは警告を出しますが、私が望む答えを与えるようです。 2つのフィルターを適用して、結果の値にJson.toJson()を使用し、結果を組み合わせることができれば、それでうまくいくでしょうか?

ただし、フィルタリングされた結果は依然としてMap [String、Object]タイプであるため、問題が発生します。

scala> Json.toJson(a1)
<console>:13: error: No Json deserializer found for type scala.collection.immutable.Map[String,Object]. Try to implement an implicit Writes or Format for this type.
          Json.toJson(a1)
21
George Hernando

Play 2.1 JSON APIは、タイプMap[String, Ojbect]のシリアライザーを提供していません。

case classの代わりに特定のタイプにMap[String, Object]Formatを定義します。

// { "val1" : "xxx", "val2" : ["a", "b", "c"] }
case class Hoge(val1: String, val2: List[String])

implicit val hogeFormat = Json.format[Hoge]

ケースクラスを作成したくない場合。次のコードは、Map [String、Object]のJSONシリアライザー/デシリアライザーを提供します。

implicit val objectMapFormat = new Format[Map[String, Object]] {

  def writes(map: Map[String, Object]): JsValue =
    Json.obj(
      "val1" -> map("val1").asInstanceOf[String],
      "val2" -> map("val2").asInstanceOf[List[String]]
    )

  def reads(jv: JsValue): JsResult[Map[String, Object]] =
    JsSuccess(Map("val1" -> (jv \ "val1").as[String], "val2" -> (jv \ "val2").as[List[String]]))
}

より動的に

import play.api.libs.json._
import play.api.libs.json.Reads._
import play.api.libs.json.Json.JsValueWrapper

implicit val objectMapFormat = new Format[Map[String, Object]] {

  def writes(map: Map[String, Object]): JsValue = 
    Json.obj(map.map{case (s, o) =>
      val ret:(String, JsValueWrapper) = o match {
        case _:String => s -> JsString(o.asInstanceOf[String])
        case _ => s -> JsArray(o.asInstanceOf[List[String]].map(JsString(_)))
      }
      ret
    }.toSeq:_*)


  def reads(jv: JsValue): JsResult[Map[String, Object]] =
    JsSuccess(jv.as[Map[String, JsValue]].map{case (k, v) =>
      k -> (v match {
        case s:JsString => s.as[String]
        case l => l.as[List[String]]
      })
    })
}

サンプルコード:

  val jv = Json.toJson(Map("val1" -> "xxx", "val2" -> List("a", "b", "c"), "val3" -> "sss", "val4" -> List("d", "e", "f")))
  println(jv)
  val jr = Json.fromJson[Map[String, Object]](jv)
  println(jr.get)

出力:

> {"val1":"xxx","val2":["a","b","c"],"val3":"sss","val4":["d","e","f"]}
> Map(val1 -> xxx, val2 -> List(a, b, c), val3 -> sss, val4 -> List(d, e, f))
26
sndyuk