Как представить динамические ключи JSON в Scala при использовании circe

Я пытаюсь представить следующий JSON как класс случая Scala:

     {
       "cars": {
          "THIS IS A DYNAMIC KEY 1": {
            "name": "bla 1",
          },
          "THIS IS A DYNAMIC KEY 2": {
            "name": "bla 2",
          }
          ...
      }

Однако у моего JSON есть динамические ключи, которые я не узнаю во время выполнения, и я хотел бы использовать circe для кодирования/декодирования. Я использую правильный способ представить это с помощью Scala?

import io.circe.generic.JsonCodec

@JsonCodec
case class Cars(cars: List[Car])

@JsonCodec
case class Car(whatShouldThisBe: CarDetails) // Not sure how to represent this?

@JsonCodec
case class CarDetails(name: String)

person Rory    schedule 08.01.2019    source источник
comment
Насколько динамичен ключ? Один из заданного/конечного списка возможностей или любая допустимая строка? Если первое, вы можете использовать sealed trait и несколько реализаций. Если последнее, смоделируйте Car как кортеж или Cars в целом как Map.   -  person Mateusz Kubuszok    schedule 08.01.2019
comment
Спасибо, ключ может быть любой допустимой строкой   -  person Rory    schedule 08.01.2019


Ответы (2)


Я думаю, вы можете просто использовать Map[String, CarDetails]. Затем ваш ADT становится:

import io.circe.generic.JsonCodec

@JsonCodec
case class Cars(cars: Map[String, CarDetails])

@JsonCodec
case class CarDetails(name: String)

Единственная трудность может заключаться в том, что вам требуется, чтобы был хотя бы один объект CarDetails, или если ноль допустим. Похоже, Цирцея поддерживает cats.data.NonEmptyMap, если это потребуется.

person Mark Kegel    schedule 08.01.2019
comment
Проголосовали за, это лучшее решение, если нет особой причины нуждаться в уровне класса случая Car. - person Travis Brown; 08.01.2019

Самый простой способ справиться с таким случаем, вероятно, состоял бы в том, чтобы изменить член cars класса case Cars на тип, подобный Map[String, CarDetails], полностью исключив класс case Car. Если вы сделаете это, ваш код будет работать точно так же, как есть (за исключением определения Car), и будет декодировать предоставленный вами пример JSON.

Если вы хотите что-то более близкое к структуре вашего класса case, вы можете сделать следующее:

import io.circe.Decoder
import io.circe.generic.JsonCodec

case class Cars(cars: List[Car])

object Cars {
  implicit val decodeCars: Decoder[Cars] =
    Decoder[Map[String, CarDetails]].prepare(_.downField("cars")).map(kvs =>
      Cars(
        kvs.map {
          case (k, v) => Car(k, v)
        }.toList
      )
    )
}

// I've added an `id` member here as a way to hold on to the JSON key.
case class Car(id: String, whatShouldThisBe: CarDetails)

@JsonCodec
case class CarDetails(name: String)

Это будет декодировать тот же JSON, но будет включать динамические ключи на уровне Car.

person Travis Brown    schedule 08.01.2019