Пользовательские кодеки
Обрабатывайте сложные структуры.
«Пользовательские кодеки» — бесплатный урок Scala for Backend Engineering & Functional Programming на CoddyKit. Это урок 4 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения Scala for Backend Engineering & Functional Programming, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс Scala for Backend Engineering & Functional Programming содержит 4 уроков всего.
Когда нужны пользовательские кодеки
Вывод кодеков охватывает распространённые случаи, но иногда JSON не повторяет структуру класса вариантов: используются устаревшие имена полей, кодируемые перечисления или такие форматы, как временные метки.
В таких случаях Decoder, Encoder или оба компонента пишутся вручную.
Декодировщик на основе курсора
Самый явный способ создать декодировщик — использовать Decoder.instance, который получает HCursor.
Переходить к полям можно с помощью downField и as, возвращая Either.
import io.circe.Decoder
case class User(name: String, age: Int)
implicit val dec: Decoder[User] = Decoder.instance { c =>
for {
n <- c.downField("full_name").as[String]
a <- c.downField("years").as[Int]
} yield User(n, a)
}Вспомогательные средства forProductN
Для простых отображений Decoder.forProduct2 выглядит короче: перечислите ключи JSON и передайте конструктор.
Вспомогательные средства доступны начиная с forProduct1 и для большего числа аргументов.
import io.circe.Decoder
implicit val dec: Decoder[User] =
Decoder.forProduct2("full_name", "years")(User.apply)Кодировщик вручную
Создайте кодировщик, симметричный декодировщику, с помощью Encoder.instance, построив объект Json из полей.
Или используйте Encoder.forProduct2, чтобы одним вызовом сопоставить поля с выбранными ключами JSON.
import io.circe.Encoder
implicit val enc: Encoder[User] =
Encoder.forProduct2("full_name", "years")(u =>
(u.name, u.age))Преобразование существующих кодеков
Обычно не приходится начинать с нуля. map адаптирует результат декодировщика, а contramap — входные данные кодировщика.
emap похож на map, но может завершиться ошибкой, возвращая Either[String, A] для проверки данных.
import io.circe.Decoder
case class Age(value: Int)
implicit val dec: Decoder[Age] =
Decoder[Int].emap { i =>
if (i >= 0) Right(Age(i)) else Left("negative age")
}Кодеки для перечислений
Запечатанные трейты с объектами вариантов моделируют перечисления. Кодируйте каждый вариант в строковый тег, а при декодировании находите вариант по этому тегу.
emap превращает неизвестный тег в понятную ошибку декодирования.
sealed trait Role
case object Admin extends Role
case object Guest extends Role
implicit val dec: Decoder[Role] = Decoder[String].emap {
case "admin" => Right(Admin)
case "guest" => Right(Guest)
case other => Left(s"unknown role: $other")
}Обратное кодирование перечисления
Объедините декодировщик перечисления с кодировщиком на основе contramap, который представляет каждый вариант его тегом.
Теперь запечатанный трейт преобразуется туда и обратно как обычная строка в JSON.
import io.circe.Encoder
implicit val enc: Encoder[Role] = Encoder[String].contramap {
case Admin => "admin"
case Guest => "guest"
}Пользовательский кодек для дат
В основном модуле Circe нет встроенных кодеков для java.time, поэтому даты — классический пример для пользовательского кодека.
Декодируйте и кодируйте даты в формате строк ISO.
import java.time.LocalDate
import io.circe.{Decoder, Encoder}
implicit val dec: Decoder[LocalDate] =
Decoder[String].map(LocalDate.parse)
implicit val enc: Encoder[LocalDate] =
Encoder[String].contramap(_.toString)Объединение в Codec
Когда оба направления готовы, объедините их в один Codec[A] с помощью Codec.from(decoder, encoder).
Так в области видимости остаётся один неявный экземпляр вместо двух отдельных.
import io.circe.Codec
implicit val roleCodec: Codec[Role] =
Codec.from(dec, enc)Дискриминаторы для ADT
Для запечатанных иерархий с данными circe-generic-extras добавляет поле-дискриминатор вместо объекта-обёртки.
Настройте его один раз и выведите кодеки для всего ADT.
import io.circe.generic.extras._
implicit val cfg: Configuration =
Configuration.default.withDiscriminator("type")
@ConfiguredJsonCodec sealed trait Shape
@ConfiguredJsonCodec case class Circle(r: Double) extends ShapeОбласть видимости и приоритет неявных экземпляров
Написанный вручную неявный кодек имеет приоритет над выведенным, пока находится в области видимости. Поместите его в сопутствующий объект, чтобы он всегда находился.
Не импортируйте одновременно auto._ и ручной экземпляр для одного типа, чтобы избежать неоднозначности.
Быстрая проверка
Проверьте, насколько хорошо вы поняли пользовательские кодеки.
Итоги
Пользовательские кодеки обрабатывают JSON, структура которого не соответствует вашим типам. Создавайте их с помощью Decoder.instance, forProductN или преобразуйте существующие через map, contramap и emap.
Перечисления и даты — распространённые случаи; объединяйте направления с помощью Codec.from, а для ADT используйте дискриминаторы.
Часто задаваемые вопросы
Урок «Пользовательские кодеки» бесплатный?
Да — полный текст урока «Пользовательские кодеки» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс Scala for Backend Engineering & Functional Programming, подпишись на CoddyKit PRO. Курс Scala for Backend Engineering & Functional Programming содержит 4 уроков всего.
Чему я научусь в уроке «Пользовательские кодеки»?
Обрабатывайте сложные структуры. Ты практикуешь Scala for Backend Engineering & Functional Programming с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.
Нужен ли мне опыт, чтобы начать Scala for Backend Engineering & Functional Programming?
Предыдущий опыт не требуется. Scala for Backend Engineering & Functional Programming на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 4 из 4.
Сколько времени занимает урок «Пользовательские кодеки»?
Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.
Можно ли писать и запускать код в этом уроке Scala for Backend Engineering & Functional Programming?
Да. Каждый урок Scala for Backend Engineering & Functional Programming включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.
Все уроки этого курса
- Разбор JSON
- Декодирование в классы вариантов
- Кодирование в JSON
- Пользовательские кодеки