Сглаживание вложенных объектов JSON с помощью Circe

Предположим, у меня есть такой объект JSON:

{
   "foo": true,
   "bar": {
      "baz": 1,
      "qux": {
        "msg": "hello world",
        "wow": [null]
      }
   }
}

И я хочу рекурсивно свести его к одному слою, с объединением клавиш с подчеркиванием:

{
   "foo": true,
   "bar_baz": 1,
   "baz_qux_msg": "hello world",
   "baz_qux_wow": [null]
}

Как я могу сделать это с помощью Circe?

(Примечание: это еще один часто задаваемый вопрос с канала Цирцеи Гиттер.)


person Travis Brown    schedule 20.09.2019    source источник


Ответы (1)


Вы можете сделать это без особых проблем в 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