Динамическое декодирование JSON с помощью Circe

Рассмотрим этот JSON:

{
  "myDocument": {
    "static_key": "value",
    "dynamic_key": "value",
    "static_key2": "value2",
    "dynamic_key2": {
      "dynamic_key3": "value3"
    }
  }
}

Документы JSON, которые я собираюсь обрабатывать, имеют некоторые статические ключи (поля, о которых я знаю, что они будут присутствовать всегда), но есть и другие, которые могут присутствовать или отсутствовать, с заранее неизвестными именами для сопоставления их с некоторыми case class .

У меня есть абсолютный путь к полю в строке (полученной из конфигурации, хранящейся в базе данных) со следующей структурой:

"myDocument.dynamic_key2.dynamic_key3"

Я знаю, что мне нужно иметь ADT для сопоставления туда и обратно, поэтому я пришел к этому:

sealed trait Data

final case class StringTuple(key: String, value: String) extends Data

object StringTuple {

  implicit val encoder: Encoder[StringTuple] = deriveEncoder[StringTuple]
  implicit val decoder: Decoder[StringTuple] = deriveDecoder[StringTuple]
}

final case class NumericTuple(key: String, value: Double) extends Data

object NumericTuple {

  implicit val encoder: Encoder[NumericTuple] = deriveEncoder[NumericTuple]
  implicit val decoder: Decoder[NumericTuple] = deriveDecoder[NumericTuple]
}

final case class DateTuple(key: String, value: OffsetDateTime) extends Data

object DateTuple {

  implicit val encoder: Encoder[DateTuple] = deriveEncoder[DateTuple]
  implicit val decoder: Decoder[DateTuple] = deriveDecoder[DateTuple]
}

final case class TransformedJson(data: Data*)

object TransformedJson {

  def apply(data: Data*): TransformedJson = new TransformedJson(data: _*)

  implicit val encoder: Encoder[TransformedJson] = deriveEncoder[TransformedJson]
  implicit val decoder: Decoder[TransformedJson] = deriveDecoder[TransformedJson]
}

Исходя из этого обсуждения, нет смысла использовать Map[String, Any] с circe , поэтому я разделил три возможных случая ключ-значение, с которыми я столкнусь при разборе отдельных полей:

  • Числовое поле, которое я буду анализировать как Double.
  • Строковое поле, проанализированное как есть (String).
  • Поле даты, проанализированное как OffsetDateTime.

По этой причине я создал три класса case, которые моделируют эти комбинации (NumericTuple, StringTuple и DateTuple), и моя идея состоит в том, чтобы создать выходной JSON следующим образом:

{
  "dynamic_key": "extractedValue",
  "dynamic_key3": "extractedValue3",
  ...
}

("Обычный", вообще без вложенности).

Моя идея состоит в том, чтобы создать список объектов Data для достижения этой цели, и у меня есть что-то вроде этого:

def extractValue(confElement: Conf, json: String) = {
    val cursor: HCursor = parse(json).getOrElse(Json.Null).hcursor
    val decodeDynamicParam = Decoder[NumericTuple].prepare(
      /*
          Here I think (not sure) that I can extract the value with the decoder,
          but, how can I extract the key name and set it, alongside with the extracted
          value?
       */
      _.downField(confElement.path)
    )
  }

Некоторые соображения:

  1. Основываясь на ответе Трэвиса на этот вопрос, я пытаюсь смоделировать JSON как можно ближе для работы с circe. Вот почему я попробовал модель tuples.
  2. Основываясь (снова) на ответе Трэвиса на этот вопрос SO, я пытаюсь использовать метод Decode.prepare(...). И вот мой вопрос...

Вопрос: как извлечь конкретное имя ключа текущей позиции курсора и сопоставить его с Tuple? Мне нужен только текущий ключ, а не весь набор ключей, который возвращает метод .keys ACursor. С помощью этого ключа я хочу вручную сопоставить Tuple с текущим именем ключа и извлеченным значением.

Подводя итог, мне нужно преобразовать структуру, которая имеет некоторые неизвестные ключи (имя и положение), извлечь их значения на основе пути, разделенного абсолютными точками, который у меня есть, и поднять как имя ключа, так и имя значения до case class, к которому я добавил суффикс Tuple.

Можете ли вы пролить свет на это?

Спасибо


person Alejandro Echeverri    schedule 22.05.2018    source источник
comment
Вы должны разделить поле на список и рекурсивно вызвать downField.   -  person James    schedule 23.05.2018