Цирцея парс json от ключей змеи - PullRequest
2 голосов
/ 26 апреля 2019

У меня есть следующий класс дел:

final case class Camel(firstName: String, lastName: String, waterPerDay: Int)

и конфигурация Circe:

object CirceImplicits {

  import io.circe.syntax._
  import io.circe.generic.semiauto._
  import io.circe.{Encoder, Decoder, Json}
  import io.circe.generic.extras.Configuration

  implicit val customConfig: Configuration =
    Configuration.default.withSnakeCaseMemberNames.withDefaults
  implicit lazy val camelEncoder: Encoder[Camel] = deriveEncoder
  implicit lazy val camelDecoder: Decoder[Camel] = deriveDecoder
}

Ничего страшного, при тестировании против этого:

val camel = Camel(firstName = "Camelbek", lastName = "Camelov", waterPerDay = 30)

private val camelJ = Json.obj(
    "firstName" -> Json.fromString("Camelbek"),
    "lastName" -> Json.fromString("Camelov"),
    "waterPerDay" -> Json.fromInt(30)
)

"Decoder" must "decode camel types" in {
    camelJ.as[Camel] shouldBe Right(camel)
}

Но этот тест не проходит:

val camel = Camel(firstName = "Camelbek", lastName = "Camelov", waterPerDay = 30)

private val camelJ = Json.obj(
    "first_name" -> Json.fromString("Camelbek"),
    "last_name" -> Json.fromString("Camelov"),
    "water_per_day" -> Json.fromInt(30)
)

"Decoder" must "decode camel types" in {
    camelJ.as[Camel] shouldBe Right(camel)
}

Как правильно настроить circe, чтобы иметь возможность разбирать json с ключами в случае змеи?

Я использую версию Circe 0.10.0

1 Ответ

3 голосов
/ 26 апреля 2019

Раствор 1

Circe получает имена полей из вашего экземпляра класса case и проходит через JSON с помощью курсора, пытается получить значение каждого имени поля и пытается преобразовать его в желаемый тип.

Это означает, что ваш декодер не сможет обрабатывать оба случая.

Решение этой проблемы - написать два декодера:

  1. Базовый декодер (производная Encoder будет работать)
  2. Кодер, который использует HCursor для навигации по вашему JSON и получения ключей регистра змей
val decoderDerived: Decoder[Camel] = deriveDecoder
val decoderCamelSnake: Decoder[Camel] = (c: HCursor) =>
    for {
      firstName <- c.downField("first_name").as[String]
      lastName <- c.downField("last_name").as[String]
      waterPerDay <- c.downField("water_per_day").as[Int]
    } yield {
      Camel(firstName, lastName, waterPerDay)
    }

Затем вы можете объединить эти два декодера в один, используя Decoder # или

implicit val decoder: Decode[Camel] = decoderDerived or decoderCamelSnake

Decoder # или попытается декодировать, используя первый декодер, и если он потерпит неудачу, то попробует второй.

Решение 2

Если вам подходит только ввод данных camel_case, вы можете использовать @ConfiguredJsonCodec из пакета "io.circe" %% "circe-generic-extras" % circeVersion. Обратите внимание, что для использования этой аннотации вам также необходимо включить плагин компилятора Paradise.

addCompilerPlugin(
  "org.scalamacros" % "paradise" % "2.1.1" cross CrossVersion.full
)
@ConfiguredJsonCodec
case class User(
  firstName: String,
  lastName: String
)

object User {
  implicit val customConfig: Configuration = Configuration.default.withSnakeCaseMemberNames
}

val userJson = User("John", "Doe").asJson
println(userJson)
// { "first_name" : "John", "last_name" : "Doe" } 

val decodedUser = decode[User](userJson.toString)
println(decodedUser)
// Right(User("John", "Doe"))

Также обратите внимание, что вам не нужно писать собственные производные декодера и кодировщика, поскольку эта конфигурация сделает это за вас.

...