在 Play Framework 的主页上,他们声称“JSON 是一等公民”。我还没有看到证明。
在我的项目中,我正在处理一些非常复杂的 JSON 结构。这只是一个非常简单的例子:
{
"key1": {
"subkey1": {
"k1": "value1"
"k2": [
"val1",
"val2"
"val3"
]
}
}
"key2": [
{
"j1": "v1",
"j2": "v2"
},
{
"j1": "x1",
"j2": "x2"
}
]
}
现在我了解到 Play 正在使用 Jackson 来解析 JSON。我在我的 Java 项目中使用 Jackson,我会做一些简单的事情:
ObjectMapper mapper = new ObjectMapper();
Map<String, Object> obj = mapper.readValue(jsonString, Map.class);
这可以很好地将我的 JSON 解析为我想要的 Map 对象 - 字符串和对象对的映射,并且可以让我轻松地将数组转换为 ArrayList
。
Scala/Play 中的相同示例如下所示:
val obj: JsValue = Json.parse(jsonString)
这反而给了我一个专有的 JsObject
type这不是我真正想要的。
我的问题是:我可以像在 Java 中那样轻松地将 Scala/Play 中的 JSON 字符串解析为 Map
而不是 JsObject
吗?
附带问题:在 Scala/Play 中使用 JsObject
代替 Map
是否有原因?
我的堆栈:Play Framework 2.2.1/Scala 2.10.3/Java 8 64bit/Ubuntu 13.10 64bit
更新:我可以看到 Travis 的回答得到了赞成,所以我想这对每个人来说都是有意义的,但我仍然看不到如何应用它来解决我的问题。假设我们有这个例子(jsonString):
[
{
"key1": "v1",
"key2": "v2"
},
{
"key1": "x1",
"key2": "x2"
}
]
好吧,根据所有指示,我现在应该放入所有我不明白其目的的样板:
case class MyJson(key1: String, key2: String)
implicit val MyJsonReads = Json.reads[MyJson]
val result = Json.parse(jsonString).as[List[MyJson]]
看起来不错,对吧?但是等一下,数组中有另一个元素完全破坏了这种方法:
[
{
"key1": "v1",
"key2": "v2"
},
{
"key1": "x1",
"key2": "x2"
},
{
"key1": "y1",
"key2": {
"subkey1": "subval1",
"subkey2": "subval2"
}
}
]
第三个元素不再匹配我定义的案例类——我又回到了第一格。我每天都可以在 Java 中使用这种更复杂的 JSON 结构,Scala 是否建议我应该简化我的 JSON 以适应它的“类型安全”策略?如果我错了,请纠正我,但我认为该语言应该为数据服务,而不是相反?
UPDATE2:解决方案是使用 Jackson 模块进行 scala(我的回答中的示例)。
最佳答案
Scala 通常不鼓励使用向下转换,Play Json 在这方面是惯用的。向下转换是一个问题,因为它使编译器无法帮助您跟踪无效输入或其他错误的可能性。一旦你得到一个 Map[String, Any]
类型的值,你就只能靠自己了——编译器无法帮助你跟踪那些 Any
的内容值可能是。
你有几个选择。第一种是使用路径运算符导航到树中您知道类型的特定点:
scala> val json = Json.parse(jsonString)
json: play.api.libs.json.JsValue = {"key1": ...
scala> val k1Value = (json \ "key1" \ "subkey1" \ "k1").validate[String]
k1Value: play.api.libs.json.JsResult[String] = JsSuccess(value1,)
这类似于以下内容:
val json: Map[String, Any] = ???
val k1Value = json("key1")
.asInstanceOf[Map[String, Any]]("subkey1")
.asInstanceOf[Map[String, String]]("k1")
但前一种方法的优势在于以更容易推理的方式失败。除了可能难以解释的ClassCastException
exception,我们只需要一个不错的JsError
value。
请注意,如果我们知道我们期望什么样的结构,我们可以在树中更高的点进行验证:
scala> println((json \ "key2").validate[List[Map[String, String]]])
JsSuccess(List(Map(j1 -> v1, j2 -> v2), Map(j1 -> x1, j2 -> x2)),)
这两个 Play 示例都建立在 type classes 的概念之上。 ——尤其是 Play 提供的 Read
类型类的实例。您还可以为自己定义的类型提供自己的类型类实例。这将允许您执行以下操作:
val myObj = json.validate[MyObj].getOrElse(someDefaultValue)
val something = myObj.key1.subkey1.k2(2)
或其他。 Play 文档(上面链接)很好地介绍了如何解决此问题,如果遇到问题,您可以随时在此处提出后续问题。
为了解决您问题中的更新,可以更改您的模型以适应 key2
的不同可能性,然后定义您自己的 Reads
实例:
case class MyJson(key1: String, key2: Either[String, Map[String, String]])
implicit val MyJsonReads: Reads[MyJson] = {
val key2Reads: Reads[Either[String, Map[String, String]]] =
(__ \ "key2").read[String].map(Left(_)) or
(__ \ "key2").read[Map[String, String]].map(Right(_))
((__ \ "key1").read[String] and key2Reads)(MyJson(_, _))
}
像这样工作:
scala> Json.parse(jsonString).as[List[MyJson]].foreach(println)
MyJson(v1,Left(v2))
MyJson(x1,Left(x2))
MyJson(y1,Right(Map(subkey1 -> subval1, subkey2 -> subval2)))
是的,这有点冗长,但它是您一次性支付的预先冗长(并且为您提供了一些很好的保证),而不是一堆可能导致令人困惑的运行时错误的强制转换。
它并不适合所有人,也可能不合你的口味——这完全没问题。您可以使用路径运算符来处理这样的情况,甚至是普通的老 jackson 。不过,我鼓励你给类型类方法一个机会——学习曲线有点陡峭,但很多人(包括我自己)都非常喜欢它。
关于java - 斯卡拉/Play : parse JSON into Map instead of JsObject,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/20029412/