首页
学习
活动
专区
工具
TVP
发布
社区首页 >问答首页 >Scala/Play:将JSON解析为Map而不是JsObject

Scala/Play:将JSON解析为Map而不是JsObject
EN

Stack Overflow用户
提问于 2013-11-17 18:01:52
回答 5查看 46.9K关注 0票数 23

在Play Framework的主页上,他们声称"JSON是一等公民“。我还没有看到这一点的证据。

在我的项目中,我要处理一些相当复杂的JSON结构。这只是一个非常简单的例子:

代码语言:javascript
复制
{
    "key1": {
        "subkey1": {
            "k1": "value1"
            "k2": [
                "val1",
                "val2"
                "val3"
            ]
        }
    }
    "key2": [
        {
            "j1": "v1",
            "j2": "v2"
        },
        {
            "j1": "x1",
            "j2": "x2"
        }
    ]
}

现在我知道Play正在使用Jackson来解析JSON。我在Java项目中使用Jackson,我会像这样做一些简单的事情:

代码语言:javascript
复制
ObjectMapper mapper = new ObjectMapper();
Map<String, Object> obj = mapper.readValue(jsonString, Map.class);

这将很好地将我的JSON解析为我想要的映射对象-字符串和对象对的映射,并允许我轻松地将数组转换为ArrayList

Scala/Play中的相同示例将如下所示:

代码语言:javascript
复制
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):

代码语言:javascript
复制
[
    {
        "key1": "v1",
        "key2": "v2"
    },
    {
        "key1": "x1",
        "key2": "x2"
    }
]

好吧,根据所有的指示,我现在应该把我不理解的那些样板放进去:

代码语言:javascript
复制
case class MyJson(key1: String, key2: String)
implicit val MyJsonReads = Json.reads[MyJson]
val result = Json.parse(jsonString).as[List[MyJson]]

看起来不错,是吧?但是稍等片刻,数组中又出现了另一个元素,它完全破坏了这种方法:

代码语言:javascript
复制
[
    {
        "key1": "v1",
        "key2": "v2"
    },
    {
        "key1": "x1",
        "key2": "x2"
    },
    {
        "key1": "y1",
        "key2": {
            "subkey1": "subval1",
            "subkey2": "subval2"
        }
    }
]

第三个元素不再与我定义的case类匹配--我又重新开始了。我每天都可以在Java中使用如此复杂的JSON结构,Scala是否建议我简化JSON以符合其“类型安全”策略?如果我错了,请纠正我,但我认为语言应该为数据服务,而不是反过来?

UPDATE2:的解决方案是使用Jackson module for scala (我的答案中的例子)。

EN

回答 5

Stack Overflow用户

回答已采纳

发布于 2013-11-18 03:01:46

我选择使用Jackson module for scala

代码语言:javascript
复制
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)
票数 27
EN

Stack Overflow用户

发布于 2013-11-17 18:55:19

Scala通常不鼓励使用向下转换,而Play Json在这方面是惯用的。向下转换是一个问题,因为它使编译器无法帮助您跟踪无效输入或其他错误的可能性。一旦获得了Map[String, Any]类型的值,就只能靠自己了-编译器无法帮助您跟踪这些Any值可能是什么。

你有几个选择。第一种方法是使用路径运算符导航到树中您知道类型的特定点:

代码语言:javascript
复制
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,)

这类似于下面的内容:

代码语言:javascript
复制
val json: Map[String, Any] = ???

val k1Value = json("key1")
  .asInstanceOf[Map[String, Any]]("subkey1")
  .asInstanceOf[Map[String, String]]("k1")

但前一种方法的优点是以更容易理解的方式失败。我们只需要得到一个很好的JsError值,而不是一个潜在的难以解释的ClassCastException异常。

请注意,如果我们知道我们期望的结构类型,我们可以在树中更高的位置进行验证:

代码语言:javascript
复制
scala> println((json \ "key2").validate[List[Map[String, String]]])
JsSuccess(List(Map(j1 -> v1, j2 -> v2), Map(j1 -> x1, j2 -> x2)),)

这两个Play示例都是基于-and的概念构建的,特别是在Play提供的Read类型类的实例上。您还可以为自己定义的类型提供自己的类型类实例。这将允许您执行类似以下的操作:

代码语言:javascript
复制
val myObj = json.validate[MyObj].getOrElse(someDefaultValue)

val something = myObj.key1.subkey1.k2(2)

或者别的什么。实演文档(上面的链接)很好地介绍了如何做到这一点,如果您遇到问题,您可以随时在此处提出后续问题。

为了解决您的问题中的更新,可以更改模型以适应key2的不同可能性,然后定义您自己的Reads实例:

代码语言:javascript
复制
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(_, _))
}

它是这样工作的:

代码语言:javascript
复制
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。我鼓励你给类型类方法一个机会--虽然学习曲线很陡峭,但很多人(包括我自己)都非常喜欢它。

票数 34
EN

Stack Overflow用户

发布于 2014-04-25 18:29:16

为了进一步参考和本着简单的精神,您可以始终使用:

代码语言:javascript
复制
Json.parse(jsonString).as[Map[String, JsValue]]

但是,对于与格式不对应的JSON字符串,这将抛出一个异常(但我假设Jackson方法也是如此)。现在可以进一步处理JsValue,如下所示:

代码语言:javascript
复制
jsValueWhichBetterBeAList.as[List[JsValue]]

我希望处理ObjectJsValue之间的区别对您来说不是问题(只是因为您抱怨JsValue是专有的)。显然,这有点像类型化语言中的动态编程,这通常不是可行的方法(Travis的答案通常是可行的),但有时我猜这很好。

票数 13
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/20029412

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档