在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,这不是我真正想要的。
我的问题是:我可以将Scala / Play中的JSON字符串解析为Map
而不是JsObject
,就像我在Java中一样容易吗?
附带问题:在Scala / Play中使用JsObject
代替Map
是否有原因?
我的堆栈:Play Framework 2.2.1 / Scala 2.10.3 / Java 8 64bit / Ubuntu 13.10 64bit
更新:我可以看到特拉维斯的答案被赞成,所以我想这对每个人都有意义,但我仍然没有看到如何应用它来解决我的问题。假设我们有这个例子(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(我的回答中的例子)。
答案 0 :(得分:31)
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
值,而不是可能难以理解的JsError
异常。
请注意,如果我们知道我们期望的结构类型,我们可以在树中更高的位置进行验证:
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)))
是的,这是一个更冗长,但它是你支付一次的前期冗长(并提供了一些很好的保证),而不是一堆可能导致混乱的运行时错误的强制转换。
并不适合所有人,也可能不符合您的口味 - 这完全没问题。您可以使用路径运算符来处理这样的情况,甚至是普通的杰克逊。我鼓励你给类型类方法一个机会,虽然有一个陡峭的学习曲线,但很多人(包括我自己)非常喜欢它。
答案 1 :(得分:26)
我选择使用Jackson module for scala。
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.scala.DefaultScalaModule
import com.fasterxml.jackson.module.scala.experimental.ScalaObjectMapper
val mapper = new ObjectMapper() with ScalaObjectMapper
mapper.registerModule(DefaultScalaModule)
val obj = mapper.readValue[Map[String, Object]](jsonString)
答案 2 :(得分:10)
为了进一步参考,本着简洁的精神,您可以随时选择:
Json.parse(jsonString).as[Map[String, JsValue]]
但是,这将为与该格式不对应的JSON字符串抛出异常(但我认为这也适用于Jackson方法)。 JsValue
现在可以进一步处理,如:
jsValueWhichBetterBeAList.as[List[JsValue]]
我希望处理Object
和JsValue
之间的区别对你来说不是问题(只是因为你抱怨JsValue
是专有的)。显然,这有点像使用打字语言的动态编程,这通常不是一条路(Travis&#39;答案通常是要走的路),但有时我很高兴猜测。
答案 3 :(得分:0)
您可以简单地提取出Json的值,而scala会为您提供相应的地图。 例如:
var myJson = Json.obj(
"customerId" -> "xyz",
"addressId" -> "xyz",
"firstName" -> "xyz",
"lastName" -> "xyz",
"address" -> "xyz"
)
假设您拥有上述类型的Json。 要将其转换为地图,请执行以下操作:
var mapFromJson = myJson.value
这会为您提供一个类型的地图: scala.collection.immutable.HashMap $ HashTrieMap
答案 4 :(得分:0)
建议阅读模式匹配和递归ADT,以便更好地理解为什么Play Json将JSON视为&#34;一等公民&#34;。
话虽这么说,许多Java-first API(如Google Java库)希望JSON反序列化为Map[String, Object]
。虽然您可以非常简单地创建自己的函数,通过模式匹配递归生成此对象,但最简单的解决方案可能是使用以下现有模式:
import com.google.gson.Gson
import java.util.{Map => JMap, LinkedHashMap}
val gson = new Gson()
def decode(encoded: String): JMap[String, Object] =
gson.fromJson(encoded, (new LinkedHashMap[String, Object]()).getClass)
如果您希望在反序列化时维护键排序,则使用LinkedHashMap(如果排序不重要,可以使用HashMap)。完整示例here。