什么`JObject(rec)< - someJArray`意味着内在的理解

时间:2015-01-09 07:49:56

标签: scala lift json4s

我正在学习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到屏幕的输出结果为:

  

列表(地图(名称 - &gt; John Derp,地址 - &gt; Jem Street 21),地图(名称 - &gt;   Scala Jo,地址 - &gt;在我甜蜜的梦中))

我想了解for循环中的行是什么意思。例如,这一行的含义是什么:

JObject(rec) <- json \ "records"

我了解json \ "records"会产生JArray个对象,但为什么它会在JObject(rec)左侧以<-的形式提取? JObject(rec)语法的含义是什么? rec变量来自哪里? JObject(rec)是否意味着从JObject输入中实例化新的rec类?

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

3 个答案:

答案 0 :(得分:7)

您有以下类型层次结构:

  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库时,存在从JValueorg.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的{​​{1}}。 MonadicJValue的结果是MonadicJValue.filter。之后执行上述步骤。

答案 1 :(得分:5)

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

你问过这一行的含义:

JObject(rec) <- json \ "records"

这是理解的一部分。

您的陈述:

  

我明白json \&#34;记录&#34;生成一个JArray对象,

略有不正确。 \ function从解析器结果List[JSObject]

中提取json
  

但是为什么它在&lt; - ?的左边以JObject(rec)的形式获取?

json \ "records"使用json4s提取器\来选择&#34;记录&#34; Json数据的成员并产生List[JObject]<-可以被解读为&#34;取自&#34;并暗示您正在迭代列表。列表的元素具有JObject类型,构造JObject(rec)应用提取器来创建一个值rec,它保存JObject的内容(其字段)。

  

它是如何以&lt; - 的左边的JObject(rec)取出的?

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

for (x <- 1 to 10)

只会在x中为我们提供1到10的值。在您的示例中,我们使用类似的迭代但是在JObject列表的内容上。

  

JObject(rec)的含义是什么?

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

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

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

  

rec变量来自哪里?

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

  

JObject(rec)是否意味着从rec输入中实例化新的JObject类?

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

所以,解释一下:

JObject(rec) <- json \ "records"

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

  

找到&#34;记录&#34;在解析的json中作为JArray并迭代它们。 JArray的元素应该是JObject类型。拉&#34; obj&#34;每个JObject的字段作为JField的列表并将其分配给名为&#34; rec&#34;的值。

希望这会让所有这一切更清楚一点?

  如果你能告诉我上面循环的Java等效代码,那么它也很有帮助。

当然,这可以做到,但这比我愿意在这里贡献的工作要多得多。您可以做的一件事是使用Scala编译代码,找到关联的.class文件,并将它们反编译为Java。对于您了解Scala简化Java编程的程度,这可能非常有启发性。 :)

  

为什么我不能这样做? for(rec&lt; -json \&#34; records&#34 ;,所以rec成为JObject。在&lt; - ?

左边的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模式之前。

如上所述,这是一个提取器,它只是使用为case类自动创建的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))))

所以:

  • 你是对的,\ operator的结果是JArray
  • &#34;迭代&#34;在JArray上只是将整个数组视为列表中唯一的值
  • 必须有一个从JArray到JObject的隐式转换,允许提取器将JArray的内容作为List [JField]生成。
  • 一旦一切都是列表,理解就会正常进行。

希望这有助于您理解这一点。

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

更新#2 : 我挖了一点,发现这里隐含的转换。罪魁祸首是\运营商。要了解json \ "records"如何变成monadic迭代事物,你必须看看这段代码:

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

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

答案 2 :(得分:2)

简单的例子,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方法就可以了。

P.S。 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))