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

Предположим, у меня есть следующие классы case, которые необходимо сериализовать как объекты JSON с помощью circe:

@JsonCodec
case class A(a1: String, a2: Option[String])

@JsonCodec
case class B(b1: Option[A], b2: Option[A], b3: Int)

Теперь мне нужно закодировать val b = B(None, Some(A("a", Some("aa")), 5) как JSON, но я хочу иметь возможность контролировать, выводится ли он как

{
  "b1": null,
  "b2": {
          "a1": "a",
          "a2": "aa"
        },
  "b3": 5
}

or

{
  "b2": {
          "a1": "a",
          "a2": "aa"
        },
  "b3": 5
}

Использование Printer dropNullKeys конфигурация, например. b.asJson.noSpaces.copy(dropNullKeys = true) приведет к исключению None из вывода, тогда как установка его на false приведет к кодированию Nones как null (см. также этот вопрос). Но как можно управлять этой настройкой для каждого поля?


person msilb    schedule 21.02.2017    source источник


Ответы (2)


Лучший способ сделать это, вероятно, просто добавить шаг постобработки в полуавтоматический кодировщик для B:

import io.circe.{ Decoder, JsonObject, ObjectEncoder }
import io.circe.generic.JsonCodec
import io.circe.generic.semiauto.{ deriveDecoder, deriveEncoder }

@JsonCodec
case class A(a1: String, a2: Option[String])
case class B(b1: Option[A], b2: Option[A], b3: Int)

object B {
  implicit val decodeB: Decoder[B] = deriveDecoder[B]
  implicit val encodeB: ObjectEncoder[B] = deriveEncoder[B].mapJsonObject(
    _.filter {
      case ("b1", value) => !value.isNull
      case _ => true
    }
  )
}

А потом:

scala> import io.circe.syntax._
import io.circe.syntax._

scala> B(None, None, 1).asJson.noSpaces
res0: String = {"b2":null,"b3":1}

Вы можете настроить аргумент фильтра, чтобы удалить любые поля с нулевым значением, которые вы хотите, из объекта JSON (здесь я просто удаляю b1 в B).

Стоит отметить, что в настоящее время вы не можете комбинировать аннотацию @JsonCodec и явно определенный экземпляр в объекте-компаньоне. Это не является неотъемлемым ограничением аннотации — мы могли бы проверять объект-компаньон на «переопределение» экземпляров во время развертывания макроса, но это значительно усложнило бы реализацию (сейчас это довольно просто). Обходной путь довольно прост (просто используйте deriveDecoder явно), но, конечно, мы будем рады рассмотреть проблему, запрашивающую поддержку для смешивания и сопоставления @JsonCodec и явных экземпляров.

person Travis Brown    schedule 21.02.2017
comment
Спасибо, это отлично работает для варианта использования. На самом деле, мне действительно нужен способ, позволяющий пользователю библиотеки контролировать, «опущено» ли значение из json или сериализовано как null. Я предполагаю, что мой единственный вариант - создать какой-то ADT для отображения 3 возможных выходов: None (опущено), Some(ANullValue) или Some(A(...)), а затем использовать собственный кодировщик, как вы предложили. - person msilb; 22.02.2017
comment
@msilb, где вы можете придумать собственный кодировщик для своего варианта использования? Я хотел бы увидеть и пример. - person osocron; 12.08.2020

Circe добавили метод dropNullValues в Json, который использует то, о чем упоминал Трэвис Браун.

def dropNulls[A](encoder: Encoder[A]): Encoder[A] =
    encoder.mapJson(_.dropNullValues)

implicit val entityEncoder: Encoder[Entity] = dropNulls(deriveEncoder)
person annedroiid    schedule 20.02.2020