Вы можете сделать это без особых проблем в Circe с помощью рекурсивного метода:
import io.circe.Json
def flatten(combineKeys: (String, String) => String)(value: Json): Json = {
def flattenToFields(value: Json): Option[Iterable[(String, Json)]] =
value.asObject.map(
_.toIterable.flatMap {
case (k, v) => flattenToFields(v) match {
case None => List(k -> v)
case Some(fields) => fields.map {
case (innerK, innerV) => combineKeys(k, innerK) -> innerV
}
}
}
)
flattenToFields(value).fold(value)(Json.fromFields)
}
Здесь наш внутренний flattenToFields метод принимает каждое значение JSON и либо возвращает None, если это значение объекта, отличное от JSON, в качестве сигнала о том, что это поле не требует сглаживания, либо Some, содержащий последовательность сглаженных полей в случае объекта JSON. .
Если у нас есть такое значение JSON:
val Right(doc) = io.circe.jawn.parse("""{
"foo": true,
"bar": {
"baz": 1,
"qux": {
"msg": "hello world",
"wow": [null]
}
}
}""")
Мы можем проверить, что flatten делает то, что мы хотим, вот так:
scala> flatten(_ + "_" + _)(doc)
res1: io.circe.Json =
{
"foo" : true,
"bar_baz" : 1,
"bar_qux_msg" : "hello world",
"bar_qux_wow" : [
null
]
}
Обратите внимание, что flattenToFields не является хвостовой рекурсией и переполняет стек для глубоко вложенных объектов JSON, но, вероятно, не раньше, чем вы достигнете нескольких тысяч уровней, поэтому на практике это вряд ли будет проблемой. Вы можете сделать его хвостовой рекурсивной без особых проблем, но за счет дополнительных накладных расходов для общих случаев, когда у вас есть только несколько уровней вложенности.
person
Travis Brown
schedule
20.09.2019