0Pricing
Scala for Backend Engineering & Functional Programming · Урок

Пользовательские кодеки

Обрабатывайте сложные структуры.

«Пользовательские кодеки» — бесплатный урок 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 — локальная установка не требуется.

Все уроки этого курса

  1. Разбор JSON
  2. Декодирование в классы вариантов
  3. Кодирование в JSON
  4. Пользовательские кодеки
← Назад к Scala for Backend Engineering & Functional Programming