`JObject(rec) <- someJArray` 在 for-comprehension 中意味着什么
What `JObject(rec) <- someJArray` means inside for-comprehension
我正在学习 Json4s 图书馆。
我有这样一个 json 片段:
{
"records":[
{
"name":"John Derp",
"address":"Jem Street 21"
},
{
"name":"Scala Jo",
"address":"in my sweet dream"
}
]
}
而且,我有 Scala 代码,它将 json 字符串转换为地图列表,如下所示:
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
到屏幕的输出为:
List(Map(name -> John Derp, address -> Jem Street 21), Map(name ->
Scala Jo, address -> in my sweet dream))
我想了解 for
循环中的行是什么意思。例如,这一行是什么意思:
JObject(rec) <- json \ "records"
我知道 json \ "records"
生成了一个 JArray
对象,但为什么它被提取为 JObject(rec)
在 <-
的左边? JObject(rec)
语法的含义是什么? rec
变量从何而来? JObject(rec)
是否意味着从 rec
输入实例化一个新的 JObject
class?
顺便说一句,我有 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 解析器 returns 以下对象:
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
returnsList[JValue]
(即List[JObject]
)。这里 List[JValue]
的每个值通过模式匹配映射到 JObject(rec)
。
Rest 调用是一系列带有模式匹配的 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
,然后使用 MonadicJValue
的 def filter
。 MonadicJValue.filter
的结果是 List[JValue]
。执行上述步骤后。
您正在使用 Scala 进行理解,我相信大部分的困惑是关于理解如何工作的。这是 Scala 语法,用于以简洁的方式访问 monad 的 map、flatMap 和 filter 方法,以迭代集合。您将需要对 monad 和理解有一些了解才能完全理解这一点。 Scala documentation 可以提供帮助,搜索 "scala for comprehension" 也有帮助。您还需要了解 Scala 中的提取器。
你问的是这行的意思:
JObject(rec) <- json \ "records"
这是理解的一部分。
您的陈述:
I understand that the json \ "records" produces a JArray object,
有点不正确。 \函数从解析器结果中提取一个List[JSObject]
,json
but why is it fetched as JObject(rec) at left of <-?
json \ "records"
使用 json4s 提取器 \ 到 select Json 数据的 "records" 成员并产生 List[JObject]
. <-
可以读作 "is taken from" 并暗示您正在遍历列表。列表的元素具有 JObject 类型,构造 JObject(rec)
应用提取器来创建一个值 rec
,该值保存 JObject 的内容(其字段)。
how come it's fetched as JObject(rec) at left of <-?
这是迭代集合的 Scala 语法。例如,我们也可以这样写:
for (x <- 1 to 10)
这将简单地为我们提供 x
中 1 到 10 的值。在您的示例中,我们使用了类似的迭代,但遍历了 JObject 列表的内容。
What is the meaning of the JObject(rec)?
这是一个 Scala 提取器。如果查看 json4s 代码,您会发现 JObject 的定义如下:
case class JObject(obj: List[JField]) extends JValue
当我们在 Scala 中遇到案例 class 时,会自动定义两种方法:apply 和 unapply。 JObject(rec)
的含义是调用 unapply 方法并产生一个值 rec
,它对应于 JObject 构造函数(apply 方法)中的值 obj
。因此,rec
将具有 List[JField]
类型。
Where does the rec variable come from?
它来自简单地使用它,并被声明为 JObject 的 apply 方法的 obj
参数的占位符。
Does JObject(rec) mean instantiating new JObject class from rec input?
不,不是。这是因为 json \ "records"
产生的 JArray 只包含 JObject 值。
所以,要解释这个:
JObject(rec) <- json \ "records"
我们可以用英文写出如下伪代码:
Find the "records" in the parsed json as a JArray and iterate over them. The elements of the JArray should be of type JObject. Pull the "obj" field of each JObject as a list of JField and assign it to a value named "rec".
希望这能让这一切更清楚一点?
it's also helpful if you can show me the Java equivalent code for the loop above.
当然可以做到,但我愿意在这里做的工作远远多于此。您可以做的一件事是使用 Scala 编译代码,找到关联的 .class 文件,并将它们反编译为 Java。这可能对您了解 Scala 在 Java 上简化了多少编程很有启发。 :)
why I can't do this? for ( rec <- json \ "records", so rec become JObject. What is the reason of JObject(rec) at the left of <- ?
你可以!但是,您随后需要获取 JObject 的内容。你可以这样写 for 理解:
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)
意思相同,但更长。
I just want to understand what does the N(x) pattern mean, because I only ever see for (x <- y pattern before.
如上所述,这是一个提取器,它只是使用为案例 classes 自动创建的 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 上的 "iteration" 只是将整个数组视为列表中的唯一值
- 必须存在从 JArray 到 JObject 的隐式转换,允许提取器将 JArray 的内容生成为 List[JField]。
- 一旦所有内容都是列表,for 推导将照常进行。
希望对您的理解有所帮助。
有关作业中模式匹配的更多信息,请尝试 this blog
更新#2:
我进一步挖掘以发现隐式转换在这里起作用。罪魁祸首是 \ 运算符。要了解 json \ "records"
是如何变成 monadic 可迭代的东西,你必须看这段代码:
- org.json4s package object:这一行声明了从
JValue
到 MonadicJValue
的隐式转换。那么什么是 MonadicJValue
?
- org.json4s.MonadicJValue:这定义了所有使 JValues 在 for comprehension 中可迭代的东西:filter、map、flatMap 并且还提供了 \ 和 \\ 类似 XPath 的运算符
因此,基本上,使用 \ 运算符会导致以下操作序列:
- 将 json (JValue) 隐式转换为 MonadicJValue
- 在 MonadicJValue 中应用 \ 运算符以生成 JArray("records")
- 将 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
- 所以像 JValue
那样拥有 filter
方法就足够了。
P.S。 JValue
中没有 foreach
- 所以这行不通 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))
我正在学习 Json4s 图书馆。
我有这样一个 json 片段:
{
"records":[
{
"name":"John Derp",
"address":"Jem Street 21"
},
{
"name":"Scala Jo",
"address":"in my sweet dream"
}
]
}
而且,我有 Scala 代码,它将 json 字符串转换为地图列表,如下所示:
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
到屏幕的输出为:
List(Map(name -> John Derp, address -> Jem Street 21), Map(name -> Scala Jo, address -> in my sweet dream))
我想了解 for
循环中的行是什么意思。例如,这一行是什么意思:
JObject(rec) <- json \ "records"
我知道 json \ "records"
生成了一个 JArray
对象,但为什么它被提取为 JObject(rec)
在 <-
的左边? JObject(rec)
语法的含义是什么? rec
变量从何而来? JObject(rec)
是否意味着从 rec
输入实例化一个新的 JObject
class?
顺便说一句,我有 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 解析器 returns 以下对象:
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
returnsList[JValue]
(即List[JObject]
)。这里 List[JValue]
的每个值通过模式匹配映射到 JObject(rec)
。
Rest 调用是一系列带有模式匹配的 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
,然后使用 MonadicJValue
的 def filter
。 MonadicJValue.filter
的结果是 List[JValue]
。执行上述步骤后。
您正在使用 Scala 进行理解,我相信大部分的困惑是关于理解如何工作的。这是 Scala 语法,用于以简洁的方式访问 monad 的 map、flatMap 和 filter 方法,以迭代集合。您将需要对 monad 和理解有一些了解才能完全理解这一点。 Scala documentation 可以提供帮助,搜索 "scala for comprehension" 也有帮助。您还需要了解 Scala 中的提取器。
你问的是这行的意思:
JObject(rec) <- json \ "records"
这是理解的一部分。
您的陈述:
I understand that the json \ "records" produces a JArray object,
有点不正确。 \函数从解析器结果中提取一个List[JSObject]
,json
but why is it fetched as JObject(rec) at left of <-?
json \ "records"
使用 json4s 提取器 \ 到 select Json 数据的 "records" 成员并产生 List[JObject]
. <-
可以读作 "is taken from" 并暗示您正在遍历列表。列表的元素具有 JObject 类型,构造 JObject(rec)
应用提取器来创建一个值 rec
,该值保存 JObject 的内容(其字段)。
how come it's fetched as JObject(rec) at left of <-?
这是迭代集合的 Scala 语法。例如,我们也可以这样写:
for (x <- 1 to 10)
这将简单地为我们提供 x
中 1 到 10 的值。在您的示例中,我们使用了类似的迭代,但遍历了 JObject 列表的内容。
What is the meaning of the JObject(rec)?
这是一个 Scala 提取器。如果查看 json4s 代码,您会发现 JObject 的定义如下:
case class JObject(obj: List[JField]) extends JValue
当我们在 Scala 中遇到案例 class 时,会自动定义两种方法:apply 和 unapply。 JObject(rec)
的含义是调用 unapply 方法并产生一个值 rec
,它对应于 JObject 构造函数(apply 方法)中的值 obj
。因此,rec
将具有 List[JField]
类型。
Where does the rec variable come from?
它来自简单地使用它,并被声明为 JObject 的 apply 方法的 obj
参数的占位符。
Does JObject(rec) mean instantiating new JObject class from rec input?
不,不是。这是因为 json \ "records"
产生的 JArray 只包含 JObject 值。
所以,要解释这个:
JObject(rec) <- json \ "records"
我们可以用英文写出如下伪代码:
Find the "records" in the parsed json as a JArray and iterate over them. The elements of the JArray should be of type JObject. Pull the "obj" field of each JObject as a list of JField and assign it to a value named "rec".
希望这能让这一切更清楚一点?
it's also helpful if you can show me the Java equivalent code for the loop above.
当然可以做到,但我愿意在这里做的工作远远多于此。您可以做的一件事是使用 Scala 编译代码,找到关联的 .class 文件,并将它们反编译为 Java。这可能对您了解 Scala 在 Java 上简化了多少编程很有启发。 :)
why I can't do this? for ( rec <- json \ "records", so rec become JObject. What is the reason of JObject(rec) at the left of <- ?
你可以!但是,您随后需要获取 JObject 的内容。你可以这样写 for 理解:
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)
意思相同,但更长。
I just want to understand what does the N(x) pattern mean, because I only ever see for (x <- y pattern before.
如上所述,这是一个提取器,它只是使用为案例 classes 自动创建的 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 上的 "iteration" 只是将整个数组视为列表中的唯一值
- 必须存在从 JArray 到 JObject 的隐式转换,允许提取器将 JArray 的内容生成为 List[JField]。
- 一旦所有内容都是列表,for 推导将照常进行。
希望对您的理解有所帮助。
有关作业中模式匹配的更多信息,请尝试 this blog
更新#2:
我进一步挖掘以发现隐式转换在这里起作用。罪魁祸首是 \ 运算符。要了解 json \ "records"
是如何变成 monadic 可迭代的东西,你必须看这段代码:
- org.json4s package object:这一行声明了从
JValue
到MonadicJValue
的隐式转换。那么什么是MonadicJValue
? - org.json4s.MonadicJValue:这定义了所有使 JValues 在 for comprehension 中可迭代的东西:filter、map、flatMap 并且还提供了 \ 和 \\ 类似 XPath 的运算符
因此,基本上,使用 \ 运算符会导致以下操作序列: - 将 json (JValue) 隐式转换为 MonadicJValue - 在 MonadicJValue 中应用 \ 运算符以生成 JArray("records") - 将 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
- 所以像 JValue
那样拥有 filter
方法就足够了。
P.S。 JValue
中没有 foreach
- 所以这行不通 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))