Вы можете сделать это без особых усилий в 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, но, вероятно, нет, пока вы не достигнете нескольких тысяч уровней, так что на практике это вряд ли будет проблемой.Вы можете сделать его рекурсивным без особых проблем, но за счет дополнительных затрат в общих случаях, когда у вас есть только несколько уровней вложенности.