JSON в XML в Scala и работа с опцией Option () - PullRequest
2 голосов
/ 15 февраля 2012

Обратите внимание на следующее из интерпретатора Scala:

scala> JSON.parseFull("""{"name":"jack","greeting":"hello world"}""")
res6: Option[Any] = Some(Map(name -> jack, greeting -> hello world))

Почему Карта возвращается в Some ()? И как мне с этим работать?

Я хочу поместить значения в шаблон xml:

<test>
  <name>name goes here</name>
  <greeting>greeting goes here</greeting>
</test>

Как в Scala можно получить мою карту из Some (вещь) и получить эти значения в xml?

Ответы [ 4 ]

6 голосов
/ 15 февраля 2012

Вы, вероятно, должны использовать что-то вроде этого:

res6 collect { case x: Map[String, String] => renderXml(x) }

Где:

def renderXml(m: Map[String, String]) = 
  <test><name>{m.get("name") getOrElse ""}</name></test>

Метод collect для Option[A] занимает PartialFunction[A, B] и является комбинацией filter (с помощью предиката) и map (с помощью функции).То есть:

opt collect pf
opt filter (a => pf isDefinedAt a) map (a => pf(a))

Оба эквивалентны.Если у вас есть необязательное значение , вы должны использовать map, flatMap, filter, collect и т. Д. Для преобразования опции в вашей программе, избегая извлечения опциисодержимое либо с помощью сопоставления с шаблоном, либо с помощью метода get.Вы должны никогда , когда-либо использовать Option.get - это канонический признак того, что вы делаете это неправильно .Следует избегать сопоставления с образцом, поскольку он представляет собой форк в вашей программе и, следовательно, увеличивает цикломатическую сложность - единственный раз, когда вы захотите сделать это, может быть для производительности

На самом деле у вас есть проблема, что результатом метода parseJSON является Option[Any] (причина в том, что Option, по-видимому, в том, что синтаксический анализ может завершиться неудачно, а Option болееизящный способ обработки null чем, ну, null).

Но проблема с моим кодом выше состоит в том, что case x: Map[String, String] нельзя проверить во время выполнения из-за стирания типа (то есть scala может проверить, чтоопция содержит Map, но не означает, что оба параметра типа Map оба String. Код выдаст вам предупреждение unchecked .

6 голосов
/ 15 февраля 2012

Возвращается Option, поскольку parseFull имеет различные возможные возвращаемые значения в зависимости от входа или может вообще не выполнить синтаксический анализ ввода (давая None). Таким образом, кроме необязательного Map, который связывает ключи со значениями, необязательный List также может быть возвращен, если строка JSON обозначает массив.

Пример:

scala> import scala.util.parsing.json.JSON._
import scala.util.parsing.json.JSON._

scala> parseFull("""{"name":"jack"}""")
res4: Option[Any] = Some(Map(name -> jack))

scala> parseFull("""[ 100, 200, 300 ]""")
res6: Option[Any] = Some(List(100.0, 200.0, 300.0))

Вам может понадобиться сопоставление с шаблоном , чтобы достичь того, что вы хотите, например:

scala> parseFull("""{"name":"jack","greeting":"hello world"}""") match {
     |   case Some(m) => Console println ("Got a map: " + m)
     |   case _ =>
     | }
Got a map: Map(name -> jack, greeting -> hello world)

Теперь, если вы хотите сгенерировать вывод XML, вы можете использовать приведенное выше для перебора пар ключ / значение:

import scala.xml.XML

parseFull("""{"name":"jack","greeting":"hello world"}""") match {
  case Some(m: Map[_,_]) =>
    <test>
      {
        m map { case (k,v) =>
          XML.loadString("<%s>%s</%s>".format(k,v,k))
        }
      }
    </test>

  case _ =>
}
5 голосов
/ 15 февраля 2012

parseFull возвращает Option, поскольку строка может быть недопустимой в формате JSON (в этом случае она будет возвращать None вместо Some).

Обычный способ получить значениеSome - это сопоставление с шаблоном:

result match {
    case Some(map) =>
        doSomethingWith(map)
    case None =>
        handleTheError()
}

Если вы уверены, что ввод всегда будет действительным, и вам не нужно обрабатывать случай неправильного ввода, выможно использовать метод get для Option, который будет вызывать исключение при вызове None.

4 голосов
/ 15 февраля 2012

У вас есть две отдельные проблемы.

  1. Он напечатан как Any.
  2. Ваши данные находятся внутри Option и Map.

Предположим, у нас есть данные:

val x: Option[Any] = Some(Map("name" -> "jack", "greeting" -> "hi"))

и предположим, что мы хотим вернуть соответствующий XML, если есть что вернуть, но не иначе.Затем мы можем использовать collect, чтобы собрать те части, с которыми мы знаем, как иметь дело:

val y = x collect {
  case m: Map[_,_] => m collect {
    case (key: String, value: String) => key -> value
  }
}

(обратите внимание, как мы разделили каждую запись на карте, чтобы убедиться, что она отображает строку встрока - мы не знали бы, как поступить иначе. Мы получаем:

y: Option[scala.collection.immutable.Map[String,String]] =
  Some(Map(name -> jack, greeting -> hi))

Хорошо, это лучше! Теперь, если вы знаете , какие поля вы хотите в своем XML, вы можетеспросите их:

val z = for (m <- y; name <- m.get("name"); greet <- m.get("greeting")) yield {
  <test><name>{name}</name><greeting>{greet}</greeting></test>
}

, который в этом (успешном) случае выдает

z: Option[scala.xml.Elem] =
  Some(<test><name>jack</name><greeting>hi</greeting></test>)

и в неудачном случае выдает None.

Если вы вместо этогохотите обернуть все, что вы найдете на карте, в форму <key>value</key>, это немного больше работы, потому что Scala не имеет хорошей абстракции для тегов:

val z = for (m <- y) yield <test>{ m.map { case (tag, text) => xml.Elem(null, tag, xml.Null, xml.TopScope, xml.Text(text)) }}</test>

, которая снова производит

z: Option[scala.xml.Elem] =
  Some(<test><name>jack</name><greeting>hi</greeting></test>)

(Вы можете использовать get, чтобы получить содержимое Option, но оно выдаст исключение, если Option пуст (т. Е. None).)

...