我在学 Json4s 图书馆。
我有一个像这样的json片段:
{
"records":[
{
"name":"John Derp",
"address":"Jem Street 21"
},
{
"name":"Scala Jo",
"address":"in my sweet dream"
}
]
}
而且,我有Scala代码,它将json字符串转换为List of Maps,如下所示:
import org.json4s._
import org.json4s.JsonAST._
import org.json4s.native.JsonParser
val json = JsonParser.parse( """{"records":[{"name":"John Derp","address":"Jem Street 21"},{"name":"Scala Jo","address":"in my sweet dream"}]}""")
val records: List[Map[String, Any]] = for {
JObject(rec) <- json \ "records"
JField("name", JString(name)) <- rec
JField("address", JString(address)) <- rec
} yield Map("name" -> name, "address" -> address)
println(records)
的输出 records
屏幕给出了这个:
列表(地图(名称 - > John Derp,地址 - > Jem Street 21),地图(名称 - >
Scala Jo,地址 - >在我甜蜜的梦中))
我想了解里面的线条是什么 for
循环意味着。例如,这一行的含义是什么:
JObject(rec) <- json \ "records"
我明白了 json \ "records"
产生一个 JArray
对象,但为什么它被取为 JObject(rec)
在左边 <-
?是什么意思 JObject(rec)
句法?在哪里 rec
变量来自哪里?是否 JObject(rec)
意味着实例化一个新的 JObject
来自 rec
输入?
顺便说一句,我有一个Java编程背景,所以如果你能告诉我上面循环的Java等效代码也会有所帮助。
您具有以下类型层次结构:
sealed abstract class JValue {
def \(nameToFind: String): JValue = ???
def filter(p: (JValue) => Boolean): List[JValue] = ???
}
case class JObject(val obj: List[JField]) extends JValue
case class JField(val name: String, val value: JValue) extends JValue
case class JString(val s: String) extends JValue
case class JArray(val arr: List[JValue]) extends JValue {
override def filter(p: (JValue) => Boolean): List[JValue] =
arr.filter(p)
}
您的JSON解析器返回以下对象:
object JsonParser {
def parse(s: String): JValue = {
new JValue {
override def \(nameToFind: String): JValue =
JArray(List(
JObject(List(
JField("name", JString("John Derp")),
JField("address", JString("Jem Street 21")))),
JObject(List(
JField("name", JString("Scala Jo")),
JField("address", JString("in my sweet dream"))))))
}
}
}
val json = JsonParser.parse("Your JSON")
在引擎盖下,Scala编译器生成以下内容:
val res = (json \ "records")
.filter(_.isInstanceOf[JObject])
.flatMap { x =>
x match {
case JObject(obj) => //
obj //
.withFilter(f => f match {
case JField("name", _) => true
case _ => false
}) //
.flatMap(n => obj.withFilter(f => f match {
case JField("address", _) => true
case _ => false
}).map(a => Map(
"name" -> (n.value match { case JString(name) => name }),
"address" -> (a.value match { case JString(address) => address }))))
}
}
第一行 JObject(rec) <- json \ "records"
是可能的,因为 JArray.filter
回报 List[JValue]
(即 List[JObject]
)。这里的每个值 List[JValue]
映射到 JObject(rec)
与模式匹配。
其余的调用是一系列flatMap和map(这是Scala for comprehensions的工作方式)与模式匹配。
我使用了Scala 2.11.4。
当然, match
上面的表达式是使用一系列类型检查和强制转换实现的。
更新:
当你使用 Json4s
库有一个隐式转换 JValue
至 org.json4s.MonadicJValue
。看到 package object json4s
:
implicit def jvalue2monadic(jv: JValue) = new MonadicJValue(jv)
此转换用于此处: JObject(rec) <- json \ "records"
。第一, json
转换为 MonadicJValue
, 然后 def \("records")
然后应用 def filter
用于结果 def \
是的 JValue
,然后再次隐式转换为 MonadicJValue
, 然后 def filter
的 MonadicJValue
用来。的结果 MonadicJValue.filter
是 List[JValue]
。之后执行上述步骤。
你正在使用Scala进行理解,我相信很多困惑都是关于理解如何运作的。这是Scala语法,用于以简洁的方式访问monad的map,flatMap和filter方法,以迭代集合。你需要对monad和理解有所了解才能完全理解这一点。该 Scala文档 可以提供帮助,搜索“scala for comprehension”也是如此。您还需要了解Scala中的提取器。
你问过这条线的含义:
JObject(rec) <- json \ "records"
这是理解的一部分。
你的陈述:
我知道json \“records”会生成一个JArray对象,
有点不正确。 \ function提取一个 List[JSObject]
从解析器结果来看, json
但为什么它在< - ?左边的JObject(rec)中被取出?
该 json \ "records"
使用json4s提取器\来选择Json数据的“records”成员并产生一个 List[JObject]
。该 <-
可以理解为“取自”并暗示您正在迭代列表。列表的元素具有类型JObject和构造 JObject(rec)
应用提取器来创建值, rec
,它包含JObject(其字段)的内容。
怎么会在< - ?左边的JObject(rec)中取出它?
这是用于迭代集合的Scala语法。例如,我们也可以写:
for (x <- 1 to 10)
这只会给我们1到10的值 x
。在您的示例中,我们使用类似的迭代但是在JObject列表的内容上。
JObject(rec)的含义是什么?
这是一个Scala提取器。如果你查看json4s代码,你会发现JObject的定义如下:
case class JObject(obj: List[JField]) extends JValue
当我们在Scala中有一个case类时,会自动定义两个方法:apply和unapply。的意思 JObject(rec)
然后是调用unapply方法并生成一个值, rec
,这对应于价值 obj
在JObject构造函数中(apply方法)。所以, rec
将有类型 List[JField]
。
rec变量来自哪里?
它来自于简单地使用它并被声明为占位符 obj
参数到JObject的apply方法。
JObject(rec)是否意味着从rec输入中实例化新的JObject类?
不,它没有。它的出现是因为JArray的结果 json \ "records"
仅包含JObject值。
所以,解释一下:
JObject(rec) <- json \ "records"
我们可以用英文写下面的伪代码:
在解析的json中找到“记录”作为JArray并迭代它们。 JArray的元素应该是JObject类型。将每个JObject的“obj”字段作为JField列表拉出,并将其分配给名为“rec”的值。
希望这会让所有这一切更清楚一点?
如果你能告诉我上面循环的Java等效代码,它也会很有帮助。
当然,这可以做到,但这比我愿意在这里贡献的工作要多得多。您可以做的一件事是使用Scala编译代码,找到关联的.class文件,并将它们反编译为Java。对于您了解Scala如何简化Java编程可能非常有益。 :)
为什么我不能这样做? for(rec < - json \“records”,所以rec成为JObject。在< - ?左边的JObject(rec)的原因是什么?
你可以!但是,您需要获取JObject的内容。你可以用这种方式写出for comprehension:
val records: List[Map[String, Any]] = for {
obj: JObject <- json \ "records"
rec = obj.obj
JField("name", JString(name)) <- rec
JField("address", JString(address)) <- rec
} yield Map("name" -> name, "address" -> address)
它具有相同的含义,但它更长。
我只想了解N(x)模式的含义,因为我只看到过(x < - y模式之前)。
如上所述,这是一个提取器,它只是使用为案例类自动创建的unapply方法。类似的事情在Scala的case语句中完成。
更新:
你提供的代码不能为我编译json4s-native的3.2.11版本。这个导入:
import org.json4s.JsonAST._
这个导入是多余的:
import org.json4s._
这样JObject被定义了两次。如果我删除JsonAST导入,那么它编译就好了。
为了进一步测试,我将你的代码放在一个scala文件中,如下所示:
package example
import org.json4s._
// import org.json4s.JsonAST._
import org.json4s.native.JsonParser
class ForComprehension {
val json = JsonParser.parse(
"""{
|"records":[
|{"name":"John Derp","address":"Jem Street 21"},
|{"name":"Scala Jo","address":"in my sweet dream"}
|]}""".stripMargin
)
val records: List[Map[String, Any]] = for {
JObject(rec) <- json \ "records"
JField("name", JString(name)) <- rec
JField("address", JString(address)) <- rec
} yield Map("name" -> name, "address" -> address)
println(records)
}
然后启动Scala REPL会话以调查:
scala> import example.ForComprehension
import example.ForComprehension
scala> val x = new ForComprehension
List(Map(name -> John Derp, address -> Jem Street 21), Map(name -> Scala Jo, address -> in my sweet dream))
x: example.ForComprehension = example.ForComprehension@5f9cbb71
scala> val obj = x.json \ "records"
obj: org.json4s.JValue = JArray(List(JObject(List((name,JString(John Derp)), (address,JString(Jem Street 21)))), JObject(List((name,JString(Scala Jo)), (address,JString(in my sweet dream))))))
scala> for (a <- obj) yield { a }
res1: org.json4s.JValue = JArray(List(JObject(List((name,JString(John Derp)), (address,JString(Jem Street 21)))), JObject(List((name,JString(Scala Jo)), (address,JString(in my sweet dream))))))
scala> import org.json4s.JsonAST.JObject
for ( JObject(rec) <- obj ) yield { rec }
import org.json4s.JsonAST.JObject
scala> res2: List[List[org.json4s.JsonAST.JField]] = List(List((name,JString(John Derp)), (address,JString(Jem Street 21))), List((name,JString(Scala Jo)), (address,JString(in my sweet dream))))
所以:
- 你是对的,\ _运算符的结果是JArray
- JArray上的“迭代”只是将整个数组视为列表中的唯一值
- 必须有从JArray到JObject的隐式转换,允许提取器将JArray的内容作为List [JField]生成。
- 一旦所有内容都是列表,理解就会正常进行。
希望有助于您理解这一点。
有关分配中模式匹配的更多信息,请尝试 这个博客
更新#2:
我挖了一点,发现这里隐含的转换。罪魁祸首是\运营商。要了解如何 json \ "records"
变成一个monadic迭代的东西,你必须看看这段代码:
因此,基本上,使用\ _运算符会产生以下一系列操作:
- 隐式将json(JValue)转换为MonadicJValue
- 在MonadicJValue中应用\运算符以产生JArray(“记录”)
- 隐式将JArray转换为MonadicJValue
- 使用MonadicJValue.filter和MonadicJValue.map方法实现for comprehension
只是简化的例子,如何for-comprehesion在这里工作:
scala> trait A
defined trait A
scala> case class A2(value: Int) extends A
defined class A2
scala> case class A3(value: Int) extends A
defined class A3
scala> val a = List(1,2,3)
a: List[Int] = List(1, 2, 3)
scala> val a: List[A] = List(A2(1),A3(2),A2(3))
a: List[A] = List(A2(1), A3(2), A2(3))
所以这里只是:
scala> for(A2(rec) <- a) yield rec //will return and unapply only A2 instances
res34: List[Int] = List(1, 3)
这相当于:
scala> a.collect{case A2(rec) => rec}
res35: List[Int] = List(1, 3)
Collect
是基于 filter
- 所以它足够了 filter
方法为 JValue
具有。
附:没有 foreach
在 JValue
- 所以这不行 for(rec <- json \ "records") rec
。但是还有 map
,这样会: for(rec <- json \ "records") yield rec
如果你需要你的 for
没有模式匹配:
for {
rec <- (json \ "records").filter(_.isInstanceOf[JObject]).map(_.asInstanceOf[JObject])
rcobj = rec.obj
name <- rcobj if name._1 == "name"
address <- rcobj if address._1 == "address"
nm = name._2.asInstanceOf[JString].s
vl = address._2.asInstanceOf[JString].s
} yield Map("name" -> nm, "address" -> vl)
res27: List[scala.collection.immutable.Map[String,String]] = List(Map(name -> John Derp, address -> Jem Street 21), Map(name -> Scala Jo, address -> in my sweet dream))