【问题标题】:What `JObject(rec) <- someJArray` means inside for-comprehension`JObject(rec) <- someJArray` 在理解中意味着什么
【发布时间】:2015-03-07 12:08:55
【问题描述】:

我正在学习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 的屏幕输出如下:

列表(地图(名称 -> 约翰·德普,地址 -> 杰姆街 21 号),地图(名称 -> Scala Jo,地址 -> 在我的美梦中))

我想了解for 循环内的行是什么意思。比如这行是什么意思:

JObject(rec) <- json \ "records"

我知道json \ "records" 产生一个JArray 对象,但为什么它在&lt;- 的左侧被提取为JObject(rec)JObject(rec) 语法的含义是什么? rec 变量来自哪里? JObject(rec) 是否意味着从 rec 输入实例化一个新的 JObject 类?

顺便说一句,我有 Java 编程背景,所以如果你能给我看一下上面循环的 Java 等效代码,也会很有帮助。

【问题讨论】:

  • 它产生JArrayJObjects。所以当&lt;-遍历它时,就可以将JObjects'的内容提取到rec变量中。
  • @GáborBakos:但我真的不明白这怎么可能。因为 JArray 不是 List 或可迭代对象。另外,为什么我不能这样做? for ( rec &lt;- json \ "records",所以rec 变成JObject&lt;-左边的JObject(rec)是什么原因?
  • 我不得不承认还没有检查过 json4s 的来源。 JObject(rec) - 我认为 - 是 unapply 方法的结果,因此在此处声明了 rec。如果JArray 有一个flatMap 方法,它应该只适用于理解。抱歉,我还没有足够的 json4s 经验来回答你的问题。希望其他人能给你一个正确的答案。
  • @GáborBakos:这里是source code,JArray 只有valuesapply 方法。看来您了解for (N(x) &lt;- y 模式(假设 N 是一个类)。你能给我解释一下模式吗?我只是想了解N(x) 模式是什么意思,因为我以前只见过for (x &lt;- y 模式。
  • 这里:github.com/json4s/json4s/blob/scala_2.10/ast/src/main/scala/org/… JObject 是一个case class,它会自动获得一个unapply 方法,因此它可以用于模式匹配/提取器,就像在变量声明中一样。请参阅:不耐烦的 Scala 14.8。

标签: scala lift json4s


【解决方案1】:

您有以下类型层次结构:

  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) &lt;- json \ "records" 是可能的,因为JArray.filter 返回List[JValue](即List[JObject])。这里List[JValue] 的每个值都通过模式匹配映射到JObject(rec)

Rest 调用是一系列带有模式匹配的 flatMap 和 map(这是 Scala 用于理解的工作方式)。

我使用的是 Scala 2.11.4。

当然,上面的match 表达式是使用一系列类型检查和强制转换来实现的。

更新:

当您使用 Json4s 库时,存在从 JValueorg.json4s.MonadicJValue 的隐式转换。见package object json4s

implicit def jvalue2monadic(jv: JValue) = new MonadicJValue(jv)

此处使用此转换:JObject(rec) &lt;- json \ "records"。首先将json转换为MonadicJValue,然后应用def \("records"),然后在def \的结果上使用def filter,即JValue,然后再次隐式转换为MonadicJValue,然后使用MonadicJValuedef filterMonadicJValue.filter 的结果是 List[JValue]。然后执行上述步骤。

【讨论】:

  • 方法 filter 仅在 for 理解中调用 JArray 并使用其结果 List[JValue]
  • JArray 扩展 JValue (scala-tools.org/mvnsites/liftweb-2.2/framework/scaladocs/net/…)。它包含filter 方法。它是 Lift Json API。
  • >> 另外,我只是再次查看源代码,JValue/JArray 类中也没有 def \ 方法。 (org.json4s.MonadicJValuedef \'. Your json`对象被隐式转换为MonadicJValue' which has def`和def filter)。
  • org.json4s.ExtractableJsonAstNodejvalue2extractable 的结果)没有'def \',因此使用了org.json4s.MonadicJValue
  • 实际上您对 scala 编译器输出的解释有点错误,因为 JValue 实际上没有 flatMap
【解决方案2】:

您正在使用 Scala 进行理解,我相信很多困惑是关于理解如何工作的。这是 Scala 语法,用于以简洁的方式访问 monad 的 map、flatMap 和 filter 方法以迭代集合。为了完全理解这一点,您将需要对 monad 和理解有所了解。 Scala documentation 可以提供帮助,搜索“scala for comprehension”也可以提供帮助。您还需要了解 Scala 中的提取器。

你问了这行的意思:

JObject(rec) <- json \ "records"

这是 for 理解的一部分。

您的陈述:

我知道json\“records”会产生一个JArray对象,

有点不正确。 \ 函数从解析器结果中提取List[JSObject]json

但是为什么它在

json \ "records" 使用 json4s 提取器 \ 来选择 Json 数据的“记录”成员并生成 List[JObject]&lt;- 可以读作“取自”并暗示您正在遍历列表。列表的元素具有 JObject 类型,构造 JObject(rec) 应用提取器创建一个值 rec,该值包含 JObject 的内容(其字段)。

它怎么会在

这是迭代集合的 Scala 语法。例如,我们也可以这样写:

for (x <- 1 to 10)

这将简单地为我们提供x 中从 1 到 10 的值。在您的示例中,我们使用了类似类型的迭代,但覆盖了 JObjects 列表的内容。

JObject(rec) 是什么意思?

这是一个 Scala 提取器。如果你查看 json4s 代码你会发现 JObject 是这样定义的:

case class JObject(obj: List[JField]) extends JValue

当我们在 Scala 中有一个案例类时,会自动定义两个方法:apply 和 unapply。那么JObject(rec) 的含义是调用unapply 方法并产生一个值rec,它对应于JObject 构造函数(apply 方法)中的值obj。因此,rec 将具有 List[JField] 类型。

rec 变量从何而来?

它来自于简单地使用它,并被声明为 JObject 的 apply 方法的 obj 参数的占位符。

JObject(rec) 是指从 rec 输入实例化新的 JObject 类吗?

不,它没有。这是因为 json \ "records" 产生的 JArray 只包含 JObject 值。

所以,解释一下:

JObject(rec) <- json \ "records"

我们可以用英文编写如下伪代码:

在解析的 json 中作为 JArray 查找“记录”并对其进行迭代。 JArray 的元素应该是 JObject 类型。将每个 JObject 的“obj”字段作为 JField 列表拉出,并将其分配给名为“rec”的值。

希望这能让这一切变得更清楚吗?

如果您能向我展示上述循环的 Java 等效代码,也会很有帮助。

当然,这是可以做到的,但是这比我愿意在这里贡献的要多得多。您可以做的一件事是使用 Scala 编译代码,找到相关的 .class 文件,然后将它们反编译为 Java。这可能对您了解 Scala 在多大程度上简化了 Java 编程很有指导意义。 :)

为什么我不能这样做? for ( rec

你可以!但是,您随后需要获取 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)

它的含义相同,但更长。

我只是想了解 N(x) 模式是什么意思,因为我以前只见过 for (x

如上所述,这是一个提取器,它只是使用为案例类自动创建的 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] 生成。
  • 一旦所有内容都是 List,for 理解就会正常进行。

希望对您理解这一点有所帮助。

有关作业中模式匹配的更多信息,请尝试this blog

更新 #2: 我又挖了一点,以发现这里的隐式转换。罪魁祸首是 \ 运算符。要了解json \ "records" 是如何变成一元可迭代的东西,您必须查看以下代码:

  • org.json4s package object:这一行声明了从JValueMonadicJValue 的隐式转换。那么MonadicJValue 是什么?
  • org.json4s.MonadicJValue:这定义了使 JValues 在 for comprehension 中可迭代的所有内容:filter、map、flatMap 并且还提供了 \ 和 \\ 类似 XPath 的运算符

因此,从本质上讲,使用 \ 运算符会导致以下操作序列: - 将 json (JValue) 隐式转换为 MonadicJValue - 在 MonadicJValue 中应用 \ 运算符以产生 JArray(“记录”) - 将 JArray 隐式转换为 MonadicJValue - 使用 MonadicJValue.filter 和 MonadicJValue.map 方法来实现 for 理解

【讨论】:

  • 感谢您的全面回答。但我仍然没有在json \ "records" 的一部分得到它,因为它返回JArray 而不是List[JObject]。 AFAIK,JArray 不是一个集合,那么它怎么可能通过&lt;- 进行迭代呢?
  • (suud 和我怀疑 org.json4s.jvalue2monadic (github.com/json4s/json4s/blob/scala_2.10/core/src/main/scala/…) 正在行动,但似乎可能是其他隐含的原因。)
  • 我相信 \ 只是为数组返回一个 List[JValue] ,就像 \ 将为一个对象返回一个 Map[String,JValue] 。我更新了我的答案以反映这一点。
  • 但是如果在 RPEL 上输入 json \ "records",它会给出如下输出: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))))))。所以你可以看到json \ "records"JArray。怎么会转成List[JObject]
  • 我使用 REPL 进行了调查。请参阅我的答案的更新。
【解决方案3】:

只是一个简化的例子,这里的理解是如何工作的:

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 方法就足够了。

附: JValue 中没有 foreach - 所以这不起作用 for(rec &lt;- json \ "records") rec。但是有map,所以会:for(rec &lt;- 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))

【讨论】:

  • 谢谢,你的例子非常简单明了:)。但是关于 JValue 中的 foreach,当我尝试 for(rec &lt;- json \ "records") yield rec 时,我在屏幕中得到了结果:res29: org.json4s.JValue = JArray(List(JObject(List((id,JString(001)), (desc,JString(test1)))), JObject(List((id,JString(002)),(desc,JString(in test2))))))。也许您误认为for(rec &lt;- json \ "records") println(rec)
  • 哦。我的错 - 我在回答中写了yield,但实际上并不是这个意思:)
  • for(rec &lt;- (json \ "records").filter(_.isInstanceOf[JObject]).map(_.asInstanceOf[JObject]); rcobj = rec.obj; name &lt;- rcobj.find(_._1 == "name"); addr &lt;- rcobj.find(_._1 == "address"); nm = name._2.asInstanceOf[JString].s; vl = addr._2.asInstanceOf[JString].s) yield Map("name" -&gt; nm, "address" -&gt; vl) res27: List[scala.collection.immutable.Map[String,String]] = List(Map(name -&gt; John Derp, address -&gt; Jem Street 21), Map(name -&gt; Scala Jo, address -&gt; in my sweet dream)) 我用分号只是为了写成单行
  • 同样,flatMap 对 monad 来说已经足够了。如果你有 map 只是它只是一个 Functor。
  • Scalaz - 实际上是 Scala 采用的 Haskell 库
猜你喜欢
  • 2023-03-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-02-23
  • 1970-01-01
  • 2020-11-04
相关资源
最近更新 更多