Метод fold
в Json
позволяет вам выполнять эту операцию довольно кратко (и таким образом, чтобы обеспечить исчерпывающую способность, точно так же, как сопоставление с образцом на запечатанной характеристике):
import io.circe.Json
def transformToXMLString(js: Json): String = js.fold(
"",
_.toString,
_.toString,
identity,
_.map(transformToXMLString(_)).mkString(""),
_.toMap.map {
case (k, v) => s"<${k}>${transformToXMLString(v)}</${k}>"
}.mkString("")
)
И затем:
scala> import io.circe.parser.parse
import io.circe.parser.parse
scala> transformToXMLString(parse(json).right.get)
res1: String = <root><sampleboolean>true</sampleboolean><sampleobj><anInt>1</anInt><aString>string</aString></sampleobj><objarray><v1>1</v1><v2>2</v2></objarray></root>
Точно такой же результат, как и у вашей реализации, но с меньшим количеством символов и без учета личных деталей реализации.
Таким образом, ответ «use * 1011»* "(или asX
методы, предложенные в другом ответе - этот подход более гибок, но в целом, вероятно, будет менее идиоматичным и более многословным).Если вам не безразлично, почему мы приняли решение о проектировании в Цирцее, чтобы не показывать конструкторы, вы можете перейти к концу этого ответа, но этот вопрос часто возникает, поэтому я также хочу затронуть несколько связанных с этим вопросов.first.
Примечание по поводу именования
Обратите внимание, что использование имени "fold" для этого метода унаследовано от Argonaut и, возможно, неточно.Когда мы говорим о катаморфизмах (или сгибах) для рекурсивных алгебраических типов данных, мы имеем в виду функцию, в которой мы не видим тип ADT в аргументах передаваемых нами функций. Например, сигнатура сгиба для списковвыглядит следующим образом:
def foldLeft[B](z: B)(op: (B, A) => B): B
Не так:
def foldLeft[B](z: B)(op: (List[A], A) => B): B
Поскольку io.circe.Json
является рекурсивным ADT, его метод fold
действительно должен выглядеть следующим образом:
def properFold[X](
jsonNull: => X,
jsonBoolean: Boolean => X,
jsonNumber: JsonNumber => X,
jsonString: String => X,
jsonArray: Vector[X] => X,
jsonObject: Map[String, X] => X
): X
Вместо:
def fold[X](
jsonNull: => X,
jsonBoolean: Boolean => X,
jsonNumber: JsonNumber => X,
jsonString: String => X,
jsonArray: Vector[Json] => X,
jsonObject: JsonObject => X
): X
Но на практике первое кажется менее полезным, поэтому Цирцея предоставляет только второе (если вы хотите выполнить повторение, вы должны сделать это вручную), и следует за Аргонавтом вназывая это fold
.Это всегда заставляло меня чувствовать себя немного неловко, и имя может измениться в будущем.
Дополнительное замечание о производительности
В некоторых случаях создание шести функций, ожидаемых fold
, может быть чрезмерно дорогимТаким образом, circe также позволяет объединять операции вместе:
import io.circe.{ Json, JsonNumber, JsonObject }
val xmlTransformer: Json.Folder[String] = new Json.Folder[String] {
def onNull: String = ""
def onBoolean(value: Boolean): String = value.toString
def onNumber(value: JsonNumber): String = value.toString
def onString(value: String): String = value
def onArray(value: Vector[Json]): String =
value.map(_.foldWith(this)).mkString("")
def onObject(value: JsonObject): String = value.toMap.map {
case (k, v) => s"<${k}>${transformToXMLString(v)}</${k}>"
}.mkString("")
}
И затем:
scala> parse(json).right.get.foldWith(xmlTransformer)
res2: String = <root><sampleboolean>true</sampleboolean><sampleobj><anInt>1</anInt><aString>string</aString></sampleobj><objarray><v1>1</v1><v2>2</v2></objarray></root>
Выигрыш в производительности от использования Folder
будет зависеть от того, включен ли вы2.11 или 2.12, но если фактические операции, выполняемые над значениями JSON, являются дешевыми, можно ожидать, что версия Folder
получит примерно вдвое большую пропускную способность, чем fold
.Между прочим, это также значительно быстрее, чем сопоставление с образцом на внутренних конструкторах, по крайней мере в тестах, которые мы сделали :
Benchmark Mode Cnt Score Error Units
FoldingBenchmark.withFold thrpt 10 6769.843 ± 79.005 ops/s
FoldingBenchmark.withFoldWith thrpt 10 13316.918 ± 60.285 ops/s
FoldingBenchmark.withPatternMatch thrpt 10 8022.192 ± 63.294 ops/s
Это на 2.12.Я полагаю, что вы должны увидеть еще большую разницу в 2.11.
Дополнительное замечание по поводу оптики
Если вы действительно хотите сопоставление с образцом, circe-optics дает вам высокую оценкуальтернатива экстракторам класса case:
import io.circe.Json, io.circe.optics.all._
def transformToXMLString(js: Json): String = js match {
case `jsonNull` => ""
case jsonBoolean(b) => b.toString
case jsonNumber(n) => n.toString
case jsonString(s) => s.toString
case jsonArray(a) => a.map(transformToXMLString(_)).mkString("")
case jsonObject(o) => o.toMap.map {
case (k, v) => s"<${k}>${transformToXMLString(v)}</${k}>"
}.mkString("")
}
Это почти такой же код, что и ваша оригинальная версия, но каждый из этих экстракторов представляет собой монокль призма , который может быть составлен с другимиоптика из библиотеки Monocle .
(Недостатком этого подхода является то, что вы теряете исчерпывающую проверку, но, к сожалению, с этим ничего не поделаешь.)
Почему бы не простотематические классы
Когда я впервые начал работать с Circe, я написал следующее в документ о некоторых из моих проектных решений :
В некоторых случаях, включая большинствов данном случае тип io.circe.Json
, мы не хотим, чтобы пользователи думали, что ADT-листы имеют значимые типы.Значение JSON «является» логическим значением, либо строкой, либо единицей, либо Seq[Json]
, либо JsonNumber
, либо JsonObject
.Введение таких типов, как JString
, JNumber
и т. Д., В общедоступный API просто сбивает с толку.
Мне нужен действительно минимальный API (и особенно API, позволяющий избежать показа типов, которые не имеют смысла)) и я хотел найти место для оптимизации представления JSON.(Я также просто не хотел, чтобы люди вообще работали с JSON AST, но это была скорее проигрышная битва.) Я все еще думаю, что скрывать конструкторы было правильным решением, хотя я на самом деле не воспользовался этим преимуществом.их отсутствия в оптимизации (пока), и хотя этот вопрос часто возникает.