【问题标题】:Scala to JSON in Play Framework 2.1Play Framework 2.1 中的 Scala 到 JSON
【发布时间】:2013-01-06 05:20:22
【问题描述】:

我正在尝试在 2.1RC Play 框架中将 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)

使用此 Play Framework 页面中关于将 Scala 转换为 Json 的“提示”,我发现以下内容 (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] 过滤会给出警告,但似乎给出了我想要的答案。如果可以应用这两个过滤器,然后将 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)

【问题讨论】:

    标签: json scala playframework-2.1


    【解决方案1】:

    Play 2.1 JSON API 不为类型Map[String, Ojbect] 提供序列化程序。

    为特定类型定义case classFormat 而不是Map[String, Object]

    // { "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))
    

    【讨论】:

    • 谢谢,但我想以通用的方式进行。该示例显示了一个包含两个键值对的 Map。可能有许多对,其值为 String 或 List[String]。这个想法是将对的值转换为 JsValue,然后执行 Json.toJson(Map[String,JsValue]) 工作正常。我正在尝试从 Map[String,Object] 重新创建 Map[String,JsValue],我知道 Object 始终是 String 或 List[String] 类型。
    • @GeorgeHernando 好的。我的理解有些模糊。 Cloud 你给我看一些示例输入对象和预期的输出 JSON?
    • 好的。我在描述中添加了更多关于我正在尝试做什么的信息。
    • @GeorgeHernando 请检查我的示例代码和输出。这是你想要的吗?
    • 这很棒,会做我想做的事,但我仍然不确定为什么像下面这样的简单映射会失败:val b = a.map{ case(k,v)=> ( k, Json.toJson(v) )}; Json.toJson(b)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-12-06
    • 1970-01-01
    • 1970-01-01
    • 2013-06-18
    • 1970-01-01
    • 1970-01-01
    • 2019-04-04
    相关资源
    最近更新 更多