Java Scala/Play:将 JSON 解析为 Map 而不是 JsObject
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/20029412/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Scala/Play: parse JSON into Map instead of JsObject
提问by Caballero
On Play Framework's homepage they claim that "JSON is a first class citizen". I have yet to see the proof of that.
在 Play Framework 的主页上,他们声称“JSON 是一等公民”。我还没有看到证据。
In my project I'm dealing with some pretty complex JSON structures. This is just a very simple example:
在我的项目中,我正在处理一些非常复杂的 JSON 结构。这只是一个非常简单的例子:
{
"key1": {
"subkey1": {
"k1": "value1"
"k2": [
"val1",
"val2"
"val3"
]
}
}
"key2": [
{
"j1": "v1",
"j2": "v2"
},
{
"j1": "x1",
"j2": "x2"
}
]
}
Now I understand that Play is using Hymanson for parsing JSON. I use Hymanson in my Java projects and I would do something simple like this:
现在我明白 Play 正在使用 Hymanson 来解析 JSON。我在我的 Java 项目中使用 Hymanson,我会做一些简单的事情:
ObjectMapper mapper = new ObjectMapper();
Map<String, Object> obj = mapper.readValue(jsonString, Map.class);
This would nicely parse my JSON into Map object which is what I want - Map of string and object pairs and would allow me easily to cast array to ArrayList
.
这可以很好地将我的 JSON 解析为我想要的 Map 对象 - 字符串和对象对的映射,并且可以让我轻松地将数组转换为ArrayList
.
The same example in Scala/Play would look like this:
Scala/Play 中的相同示例如下所示:
val obj: JsValue = Json.parse(jsonString)
This instead gives me a proprietary JsObject
typewhich is not really what I'm after.
这反而给了我一个专有JsObject
类型,这并不是我真正想要的。
My question is: can I parse JSON string in Scala/Play to Map
instead of JsObject
just as easily as I would do it in Java?
我的问题是:我可以在 Scala/Play 中解析 JSON 字符串,Map
而不是JsObject
像在 Java 中那样容易吗?
Side question: is there a reason why JsObject
is used instead of Map
in Scala/Play?
附带问题:是否有理由在 Scala/Play 中JsObject
使用而不是使用Map
?
My stack: Play Framework 2.2.1 / Scala 2.10.3 / Java 8 64bit / Ubuntu 13.10 64bit
我的堆栈:Play Framework 2.2.1 / Scala 2.10.3 / Java 8 64bit / Ubuntu 13.10 64bit
UPDATE:I can see that Travis' answer is upvoted, so I guess it makes sense to everybody, but I still fail to see how that can be applied to solve my problem. Say we have this example (jsonString):
更新:我可以看到 Travis 的回答得到了赞成,所以我想这对每个人都有意义,但我仍然看不到如何应用它来解决我的问题。假设我们有这个例子(jsonString):
[
{
"key1": "v1",
"key2": "v2"
},
{
"key1": "x1",
"key2": "x2"
}
]
Well, according to all the directions, I now should put in all that boilerplate that I otherwise don't understand the purpose of:
好吧,根据所有说明,我现在应该放入所有我不明白的目的的样板:
case class MyJson(key1: String, key2: String)
implicit val MyJsonReads = Json.reads[MyJson]
val result = Json.parse(jsonString).as[List[MyJson]]
Looks good to go, huh? But wait a minute, there comes another element into the array which totally ruins this approach:
看起来不错吧?但是等一下,数组中有另一个元素完全破坏了这种方法:
[
{
"key1": "v1",
"key2": "v2"
},
{
"key1": "x1",
"key2": "x2"
},
{
"key1": "y1",
"key2": {
"subkey1": "subval1",
"subkey2": "subval2"
}
}
]
The third element no longer matches my defined case class - I'm at square one again. I am able to use such and much more complicated JSON structures in Java everyday, does Scala suggest that I should simplify my JSONs in order to fit it's "type safe" policy? Correct me if I'm wrong, but I though that language should serve the data, not the other way around?
第三个元素不再与我定义的案例类匹配 - 我又回到了第一个。我每天都可以在 Java 中使用这种更复杂的 JSON 结构,Scala 是否建议我应该简化我的 JSON 以适应它的“类型安全”策略?如果我错了,请纠正我,但我认为该语言应该为数据服务,而不是相反?
UPDATE2:Solution is to use Hymanson module for scala (example in my answer).
UPDATE2:解决方案是将 Hymanson 模块用于 Scala(我的回答中的示例)。
采纳答案by Caballero
I've chosen to use Hymanson module for scala.
我选择将Hymanson 模块用于 scala。
import com.fasterxml.Hymanson.databind.ObjectMapper
import com.fasterxml.Hymanson.module.scala.DefaultScalaModule
import com.fasterxml.Hymanson.module.scala.experimental.ScalaObjectMapper
val mapper = new ObjectMapper() with ScalaObjectMapper
mapper.registerModule(DefaultScalaModule)
val obj = mapper.readValue[Map[String, Object]](jsonString)
回答by Travis Brown
Scala in general discourages the use of downcasting, and Play Json is idiomatic in this respect. Downcasting is a problem because it makes it impossible for the compiler to help you track the possibility of invalid input or other errors. Once you've got a value of type Map[String, Any]
, you're on your own—the compiler is unable to help you keep track of what those Any
values might be.
Scala 通常不鼓励使用向下转换,而 Play Json 在这方面是惯用的。向下转换是一个问题,因为它使编译器无法帮助您跟踪无效输入或其他错误的可能性。一旦获得 type 的值Map[String, Any]
,您就靠自己了——编译器无法帮助您跟踪这些Any
值可能是什么。
You have a couple of alternatives. The first is to use the path operators to navigate to a particular point in the tree where you know the type:
你有几个选择。第一种是使用路径运算符导航到树中您知道类型的特定点:
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,)
This is similar to something like the following:
这类似于以下内容:
val json: Map[String, Any] = ???
val k1Value = json("key1")
.asInstanceOf[Map[String, Any]]("subkey1")
.asInstanceOf[Map[String, String]]("k1")
But the former approach has the advantage of failing in ways that are easier to reason about. Instead of a potentially difficult-to-interpret ClassCastException
exception, we'd just get a nice JsError
value.
但前一种方法的优点是失败的方式更容易推理。而不是一个潜在的难以解释的ClassCastException
异常,我们只会得到一个很好的JsError
值。
Note that we can validate at a point higher in the tree if we know what kind of structure we expect:
请注意,如果我们知道我们期望什么样的结构,我们可以在树的更高点进行验证:
scala> println((json \ "key2").validate[List[Map[String, String]]])
JsSuccess(List(Map(j1 -> v1, j2 -> v2), Map(j1 -> x1, j2 -> x2)),)
Both of these Play examples are built on the concept of type classes—and in particular on instances of the Read
type class provided by Play. You can also provide your own type class instances for types that you've defined yourself. This would allow you to do something like the following:
这两个 Play 示例都建立在类型类的概念之上——特别是建立在Read
Play 提供的类型类的实例上。您还可以为自己定义的类型提供自己的类型类实例。这将允许您执行以下操作:
val myObj = json.validate[MyObj].getOrElse(someDefaultValue)
val something = myObj.key1.subkey1.k2(2)
Or whatever. The Play documentation (linked above) provides a good introduction to how to go about this, and you can always ask follow-up questions here if you run into problems.
管他呢。Play 文档(上面链接)很好地介绍了如何解决这个问题,如果遇到问题,您可以随时在此处提出后续问题。
To address the update in your question, it's possible to change your model to accommodate the different possibilities for key2
, and then define your own Reads
instance:
为了解决您问题中的更新,可以更改您的模型以适应 的不同可能性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(_, _))
}
Which works like this:
它的工作原理是这样的:
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)))
Yes, this is a little more verbose, but it's up-front verbosity that you pay for once (and that provides you with some nice guarantees), instead of a bunch of casts that can result in confusing runtime errors.
是的,这有点冗长,但它是您支付一次的前期冗长(这为您提供了一些很好的保证),而不是一堆会导致混乱的运行时错误的强制转换。
It's not for everyone, and it may not be to your taste—that's perfectly fine. You can use the path operators to handle cases like this, or even plain old Hymanson. I'd encourage you to give the type class approach a chance, though—there's a steep-ish learning curve, but lots of people (including myself) very strongly prefer it.
它并不适合所有人,也可能不合您的口味——这完全没问题。您可以使用路径运算符来处理此类情况,甚至可以使用普通的 Hymanson。我鼓励你给类型类方法一个机会,虽然有一个陡峭的学习曲线,但很多人(包括我自己)非常喜欢它。
回答by Dominik Bucher
For further reference and in the spirit of simplicity, you can always go for:
为了进一步参考并本着简单的精神,您可以随时访问:
Json.parse(jsonString).as[Map[String, JsValue]]
However, this will throw an exception for JSON strings not corresponding to the format (but I assume that goes for the Hymanson approach as well). The JsValue
can now be processed further like:
但是,对于与格式不对应的 JSON 字符串,这将引发异常(但我认为这也适用于 Hymanson 方法)。的JsValue
现在可以被进一步处理,如:
jsValueWhichBetterBeAList.as[List[JsValue]]
I hope the difference between handling Object
s and JsValue
s is not an issue for you (only because you were complaining about JsValue
s being proprietary). Obviously, this is a bit like dynamic programming in a typed language, which usually isn't the way to go (Travis' answer is usually the way to go), but sometimes that's nice to have I guess.
我希望处理Object
s 和JsValue
s之间的区别对您来说不是问题(只是因为您抱怨JsValue
s 是专有的)。显然,这有点像类型化语言中的动态编程,这通常不是要走的路(Travis 的回答通常是要走的路),但有时我猜这很好。
回答by Pushp Raj Saurabh
You can simply extract out the value of a Json and scala gives you the corresponding map. Example:
您可以简单地提取 Json 的值,scala 会为您提供相应的映射。例子:
var myJson = Json.obj(
"customerId" -> "xyz",
"addressId" -> "xyz",
"firstName" -> "xyz",
"lastName" -> "xyz",
"address" -> "xyz"
)
Suppose you have the Json of above type. To convert it into map simply do:
假设您有上述类型的 Json。要将其转换为地图,只需执行以下操作:
var mapFromJson = myJson.value
This gives you a map of type : scala.collection.immutable.HashMap$HashTrieMap
这为您提供了一个类型的映射:scala.collection.immutable.HashMap$HashTrieMap
回答by Matt
Would recommend reading up on pattern matching and recursive ADTs in general to better understand of why Play Json treats JSON as a "first class citizen".
建议一般阅读模式匹配和递归 ADT,以更好地理解为什么 Play Json 将 JSON 视为“一等公民”。
That being said, many Java-first APIs (like Google Java libraries) expect JSON deserialized as Map[String, Object]
. While you can very simply create your own function that recursively generates this object with pattern matching, the simplest solution would probably be to use the following existing pattern:
话虽如此,许多 Java 优先的 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)
The LinkedHashMap is used if you would like to maintain key ordering at the time of deserialization (a HashMap can be used if ordering doesn't matter). Full example here.
如果您想在反序列化时保持键排序,则使用 LinkedHashMap(如果排序无关紧要,可以使用 HashMap)。完整示例在这里。