关于scala:`JObject(rec)<-someJArray`意味着内在理解

What `JObject(rec) <- someJArray` means inside for-comprehension

我正在学习Json4s库。

我有一个像这样的json片段:

1
2
3
4
5
6
7
8
9
10
11
12
{
   "records":[
        {
           "name":"John Derp",
           "address":"Jem Street 21"
        },
        {
           "name":"Scala Jo",
           "address":"in my sweet dream"
        }
    ]
}

而且,我有Scala代码,它将json字符串转换为Maps列表,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
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循环中的行的含义。 例如,此行的含义是什么:

1
JObject(rec) <- json "records"

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

顺便说一句,我有Java编程背景,因此如果您可以向我展示上述循环的Java等效代码,这也将有所帮助。


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

1
2
3
4
5
6
7
8
9
10
11
12
  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解析器返回以下对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
  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编译器生成以下内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
  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的工作方式)。

我使用了Scala 2.11.4。

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

更新:

使用Json4s库时,从JValueorg.json4s.MonadicJValue会有隐式转换。参见package object json4s

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

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


您正在使用Scala进行理解,我相信很多困惑是关于理解如何工作的。这是Scala语法,用于以简洁的方式访问集合的monad的map,flatMap和filter方法。您需要对单子和理解有一些了解才能完全理解。 Scala文档可以提供帮助,搜索" scala for comprehension"也可以提供帮助。您还需要了解Scala中的提取器。

好。

您询问了此行的含义:

好。

1
JObject(rec) <- json "records"

这是理解的一部分。

好。

您的声明:

好。

I understand that the json \"records" produces a JArray object,

Ok.

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

好。

but why is it fetched as JObject(rec) at left of <-?

Ok.

json \"records"使用json4s提取器选择Json数据的" records"成员并生成List[JObject]<-可以理解为"是从...获取的",表示您正在遍历列表。列表中的元素具有JObject类型,构造JObject(rec)应用提取器来创建值rec,该值保存JObject的内容(其字段)。

好。

how come it's fetched as JObject(rec) at left of <-?

Ok.

这是用于遍历集合的Scala语法。例如,我们还可以编写:

好。

1
for (x <- 1 to 10)

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

好。

What is the meaning of the JObject(rec)?

Ok.

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

好。

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

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

好。

Where does the rec variable come from?

Ok.

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

好。

Does JObject(rec) mean instantiating new JObject class from rec input?

Ok.

不,不是。之所以这样,是因为json \"records"生成的JArray仅包含JObject值。

好。

因此,要解释一下:

好。

1
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".

Ok.

希望这使所有这些变得更清楚了吗?

好。

it's also helpful if you can show me the Java equivalent code for the loop above.

Ok.

当然可以做到这一点,但这远远超出了我在此所做的贡献。您可以做的一件事是使用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 <- ?

Ok.

你可以!但是,您随后需要获取JObject的内容。您可以通过以下方式编写for理解:

好。

1
2
3
4
5
6
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.

Ok.

如上所述,这是一个提取器,仅使用为案例类自动创建的unapply方法。在Scala中的case语句中完成了类似的操作。

好。

更新:
您提供的代码无法针对json4s-native的3.2.11版本进行编译。此导入:

好。

1
import org.json4s.JsonAST._

此导入是多余的:

好。

1
import org.json4s._

这样,JObject被定义了两次。如果我删除了JsonAST导入,则可以正常编译。

好。

为了进一步测试,我将您的代码放在这样的scala文件中:

好。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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会话以进行调查:

好。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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]产生。
  • 一旦所有内容都为列表,则for理解将照常进行。
  • 好。

    希望对您的理解有所帮助。

    好。

    有关分配中模式匹配的更多信息,请尝试此博客

    好。

    更新#2:
    我在这里多挖了一点,以发现隐式转换在起作用。罪魁祸首是运算符。要了解json \"records"如何变成单子可迭代的事物,您必须查看以下代码:

    好。

  • org.json4s包对象:此行声明从JValueMonadicJValue的隐式转换。 那么什么是MonadicJValue
  • org.json4s.MonadicJValue:这定义了所有使JValues可迭代以进行理解的东西:filter,map,flatMap,还提供了和\类似XPath的运算符
  • 好。

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

    好。

    好。


    只是简化的示例,for-comprehesion在这里如何工作:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    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))

    所以这只是:

    1
    2
    scala> for(A2(rec) <- a) yield rec //will return and unapply only A2 instances
    res34: List[Int] = List(1, 3)

    等效于:

    1
    2
    scala> a.collect{case A2(rec) => rec}
    res35: List[Int] = List(1, 3)

    Collect基于filter-因此具有与JValue相同的filter方法就足够了。

    附言JValue中没有foreach-因此这在for(rec <- json \"records") rec中不起作用。但是有map,因此将:for(rec <- json \"records") yield rec

    如果您需要不带模式匹配的for

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    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))