Что "JObject (rec) <- someJArray` означает внутреннее понимание

Я изучаю 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 на экран дает следующее:

Список (Карта (имя → 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 для цикла выше.

Ответ 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) <- json \ "records" возможна, потому что JArray.filter возвращает List[JValue] (т.е. List[JObject]). Здесь каждое значение List[JValue] отображается на JObject(rec) с сопоставлением с образцом.

Звонки на вызовы - это серия flatMap и map (это как Scala для работы с пониманиями) с сопоставлением с образцом.

Я использовал Scala 2.11.4.

Конечно, выражения match, приведенные выше, реализованы с использованием серии проверок типов и отбрасываний.

UPDATE:

При использовании библиотеки 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 of MonadicJValue. Результат MonadicJValue.filter равен List[JValue]. После этого выполняются этапы, описанные выше.

Ответ 2

Вы используете Scala для понимания, и я полагаю, что большая часть путаницы заключается в том, как работать с пониманием. Это синтаксис Scala для краткого доступа к картам, методам flatMap и фильтров монады для итерации над коллекциями. Для полного понимания этого вам потребуется некоторое понимание монадов и понимания. Scala документация может помочь, и так будет поиск "scala для понимания". Вам также нужно будет узнать об экстракторах в Scala.

Вы спросили о значении этой строки:

JObject(rec) <- json \ "records"

Это часть понимания.

Ваше выражение:

Я понимаю, что json\ "records" создает объект JArray,

немного некорректен. Функция\извлекает a List[JSObject] из результата парсера, json

но почему он извлекается как JObject (rec) слева от < -?

В json \ "records" используется экстрактор json4s \, чтобы выбрать элемент записи "записи" данных Json и получить List[JObject]. <- можно прочитать как "взято из" и подразумевает, что вы повторяете список. Элементы списка имеют тип JObject, а конструктор JObject(rec) применяет экстрактор для создания значения rec, который содержит содержимое объекта JObject (его поля).

как он извлекается как JObject (rec) слева от < -?

Это синтаксис Scala для итерации по коллекции. Например, мы могли бы также написать:

for (x <- 1 to 10)

который просто дал бы нам значения от 1 до 10 в x. В вашем примере мы используем аналогичную итерацию, но над содержимым списка JObjects.

В чем смысл JObject (rec)?

Это экстрактор Scala. Если вы посмотрите в коде json4s, вы обнаружите, что JObject определяется следующим образом:

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

Когда у нас есть класс case в Scala, автоматически определяются два метода: apply and unapply. Тогда значение JObject(rec) заключается в вызове метода unapply и создании значения rec, которое соответствует значению obj в конструкторе JObject (применить метод). Таким образом, rec будет иметь тип List[JField].

Откуда берется переменная rec?

Он исходит из просто его использования и объявляется как заполнитель для параметра obj методу применения JObject.

Предоставляет ли JObject (rec) создание нового класса JObject из ввода rec?

Нет, это не так. Это происходит потому, что JArray, полученный из json \ "records", содержит только значения JObject.

Итак, чтобы интерпретировать это:

JObject(rec) <- json \ "records"

мы могли бы написать следующий псевдокод на английском языке:

Найти "записи" в анализируемом json как JArray и перебрать их. Элементы JArray должны быть типа JObject. Потяните поле "obj" каждого JObject как список JField и присвойте ему значение с именем "rec".

Надеюсь, это сделает все это более понятным?

это также полезно, если вы можете показать мне эквивалентный код Java для цикла выше.

Это можно сделать, конечно, но это гораздо больше, чем я готов сюда внести. Одна вещь, которую вы можете сделать, - скомпилировать код с помощью Scala, найти связанные файлы .class и декомпилировать их как Java. Для вас может быть очень поучительно узнать, насколько Scala упрощает программирование через Java.:)

Почему я не могу этого сделать? for (rec < - json\ "records", поэтому rec становится JObject. В чем причина JObject (rec) слева от < -?

Вы могли бы! Однако вам нужно будет получить содержимое JObject. Вы могли бы написать это понимание следующим образом:

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 pattern before.

Как объяснялось выше, это экстрактор, который просто используется методом unapply, который автоматически создается для классов case. Аналогичная ситуация выполняется в случае case в Scala.

UPDATE: Код, который вы предоставили, не компилируется для меня против версии 3.2.11 json4s-native. Этот импорт:

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 = [email protected]

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, для понимания происходит как обычно.

Надеюсь, что это поможет с пониманием этого.

Для получения дополнительной информации о сопоставлении с образцом в назначениях, попробуйте этот блог

ОБНОВЛЕНИЕ # 2: Я выкопал еще немного, чтобы обнаружить здесь неявное преобразование. Преступником является \operator. Чтобы понять, как json \ "records" превращается в монодичную итеративную вещь, вы должны посмотреть на этот код:

  • объект пакета org.json4s: эта строка объявляет неявное преобразование от JValue до MonadicJValue. Итак, что a MonadicJValue?
  • org.json4s.MonadicJValue: Это определяет все, что делает JValues ​​итерабельным в понимании: filter, map, flatMap, а также обеспечивает\и\\XPath-подобные операторы

Таким образом, по сути, использование оператора\приводит к следующей последовательности действий:  - неявно преобразовать json (JValue) в MonadicJValue  - Примените оператор\в MonadicJValue, чтобы получить JArray ( "записи" )  - неявно преобразовать JArray в MonadicJValue  - Используйте методы MonadicJValue.filter и MonadicJValue.map для реализации понимания для

Ответ 3

Просто упрощенный пример: как для-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. В 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))