在Play Framework 2.1中将Scala转换为JSON

21

我正在尝试在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, JsValue] 而不是 Map[String, Object],则 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)
1个回答

26

Play 2.1 JSON API没有为类型Map[String, Object]提供序列化程序。

应为特定类型定义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] 类型。 - George Hernando
@GeorgeHernando 好的。我的理解中还存在一些不确定性。您能否给我展示一些输入对象和期望的输出JSON的示例? - sndyuk
好的。我在描述中添加了更多关于我正在尝试做什么的信息。 - George Hernando
@GeorgeHernando 请检查我的示例代码和输出。这是你想要的吗? - sndyuk
1
这很好,可以实现我想要的功能,但我仍然不确定为什么像下面这样简单的映射会失败:val b = a.map{ case(k,v)=> (k, Json.toJson(v) )}; Json.toJson(b) - George Hernando
@GeorgeHernando Json.toJson(v) 被定义为 def toJson[T](o: T)(implicit tjs: Writes[T]): JsValue = ...v 的类型是 Object。因此我们需要一个隐式的 Writes[Object]。由于 Object 太模糊了,无法序列化,Play JSON API 无法提供隐式的 Writes[Object]。所以我们必须定义一个隐式的 Writes[Object] - sndyuk

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接