问题 什么`JObject(rec)


我在学 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等效代码也会有所帮助。


11525
2018-01-09 07:49


起源

它产生了一个 JArray 的 JObject秒。所以当 <- 通过它迭代,你可以提取 JObjects'满足于 rec 变量。 - Gábor Bakos
@GáborBakos:但我真的不明白它怎么可能。因为JArray不是List或者可迭代的对象。另外,为什么我不能这样做? for ( rec <- json \ "records"所以 rec 成为 JObject。是什么原因 JObject(rec) 在左边 <- ? - null
我不得不承认还没检查过json4s的来源。该 JObject(rec)  - 我想 - 是一个结果 unapply 方法,所以 rec 在那里宣布。如果 JArray 有一个 flatMap 方法,它应该只是为了理解。对不起,我还没有足够的json4s经验来回答你的问题。我希望别人可以给你一个正确的答案。 - Gábor Bakos
@GáborBakos:这是 源代码,JArray只有 values和 apply 方法。看来你对此有所了解 for (N(x) <- y 模式(假设N是一个类)。你能解释一下这种模式吗?我只想了解是什么 N(x) 模式意味着,因为我只看到过 for (x <- y 之前的模式。 - null
这里: github.com/json4s/json4s/blob/scala_2.10/ast/src/main/scala/org/... JObject是一个 case class 它自动得到一个 unapply 方法,因此它可以在模式匹配/提取器中使用,就像在变量声明中一样。请参阅:Scala for the Impatient 14.8。 - Gábor Bakos


答案:


您具有以下类型层次结构:

  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]。之后执行上述步骤。


7
2018-01-09 14:05



方法 filter 被召唤 JArray 只有在 for 理解及其结果 List[JValue] 用来。 - user5102379
JArray 扩展 JValue (scala-tools.org/mvnsites/liftweb-2.2/framework/scaladocs/net/...)。它包含 filter 方法。它是Lift Json API。 - user5102379
>>另外,我再次检查源代码,JValue / JArray类中也没有def \方法。 (org.json4s.MonadicJValue 具有 def \'. Your json`对象被隐式转换为 MonadicJValue' which has def`和 def filter)。 - user5102379
org.json4s.ExtractableJsonAstNode (的结果 jvalue2extractable由于这个原因,没有'def' org.json4s.MonadicJValue 用来。 - user5102379
实际上你对scala的编译器输出的解释有点不对 JValue 没有 flatMap 其实 - dk14


你正在使用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迭代的东西,你必须看看这段代码:

  • org.json4s包对象:这一行声明了一个隐式转换 JValue 至 MonadicJValue。什么是 MonadicJValue
  • org.json4s.MonadicJValue:这定义了使JValues可以在for comprehension中迭代的所有东西:filter,map,flatMap,还提供了\和\\类似XPath的运算符

因此,基本上,使用\ _运算符会产生以下一系列操作:   - 隐式将json(JValue)转换为MonadicJValue   - 在MonadicJValue中应用\运算符以产生JArray(“记录”)   - 隐式将JArray转换为MonadicJValue   - 使用MonadicJValue.filter和MonadicJValue.map方法实现for comprehension


5
2018-01-09 14:24



感谢您的全面解答。但我仍然没有得到它的一部分 json \ "records",因为它返回了 JArray 不 List[JObject]。 AFAIK,JArray不是一个集合,所以它怎么可能被迭代 <- ? - null
(suud和我怀疑 org.json4s.jvalue2monadic (github.com/json4s/json4s/blob/scala_2.10/core/src/main/scala/...)是在行动,但似乎可能是其他暗示导致它。) - Gábor Bakos
我相信\只是为一个数组返回一个List [JValue],就像\将返回一个对象的Map [String,JValue]。我更新了我的答案以反映这一点。 - Reid Spencer
但如果进入 json \ "records" 在RPEL上,它给出了这样的输出: 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]? - null
我用REPL进行了调查。请参阅我的答案的更新。 - Reid Spencer


只是简化的例子,如何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))

2
2018-01-10 17:17



谢谢,你的例子很简单明了:)。但关于JValue的foreach,当我尝试时 for(rec <- 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 <- json \ "records") println(rec)? - null
哦。我的坏 - 我写的 yield 在我的答案,但并不意味着它实际上:) - dk14
for(rec <- (json \ "records").filter(_.isInstanceOf[JObject]).map(_.asInstanceOf[JObject]); rcobj = rec.obj; name <- rcobj.find(_._1 == "name"); addr <- rcobj.find(_._1 == "address"); nm = name._2.asInstanceOf[JString].s; vl = addr._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)) 我用分号只是把它写成一行 - dk14
相同, flatMap 对于monad来说已经足够了。如果你有 map 只是它只是一个Functor。 - dk14
Scalaz - 实际上是Scask采用的Haskell库 - dk14