Всем привет!   Во время нашей работы часто приходится сталкиваться с таким форматом обмена данных как JSON, и на данный момент существует большое количество различных библиотек для JSON сериализации. Конечно, для любителей языка программирования Scala, которые хотят использовать преимущества этого языка, тоже есть такая библиотека – о ней и пойдёт речь в данной статье. https://circe.github.io/circe/ Для тех, кто хочет сразу же посмотреть, что получится в ходе статьи, добро пожаловать на GitHub со всем кодом, который рассмотрим далее. Для знакомства с Circe в рамках данной статьи нам понадобятся следующие библиотеки: Circe-core Сirce-generic Сirce-parser В данной статье была использована версия Circe - 0.12.3 Стоит начать с простого парсинга. Для этого создадим метод, который будет превращать обыкновенную строку в JSON-объект. В Circe это реализовано весьма просто: parse(str) – это метод, который уже находится в Circe и при попытке превратить нашу строку в JSON-объект, он возвращает результат в обертке Either, что позволяет получить JSON-объект или ошибку, которая возникла при парсинге. Давайте убедимся в том, что это работает. Для примера возьмём актера, его пол и список фильмов, в которых он играл: После запуска приложения видим, что все работает корректно: Если же в работе мы встретим строку в виде JSON Array, то нам нужно парсить это немного иначе. Для парсинга таких объектов нам понадобится новый метод, который будет выглядеть следующим образом: В данном случае мы используем внутренний метод Circe – asArray, который приводит наш JSON к коллекции, а мы уже конвертируем его в нужный нам List. Для примера возьмем строку, которая будет содержать список актеров. Выглядеть она будет примерно так: Давайте посмотрим, что показывает нам консоль при запуске приложения: Как можно заметить, в консоль вывелось то, что и ожидалось, а именно – List состоящий из двух объектов типа JSON. На этом с базовым парсингом мы разобрались и теперь можем перейти к более сложным вещам: к таким понятиям, как декодинг и энкодинг в Circe. Circe, как и любая иная библиотека, работающая с JSON-форматом, умеет конвертировать JSON в класс и обратно. Она предлагает такие виды конвертации, как автоматическая, полуавтоматическая и ручная. Об этом мы и поговорим далее. Для конвертации Circe использует классы Encoder и Decoder – для кодирования и декодирования соответственно. Для демонстрации конвертации класса в строку нам понадобится следующий метод: Как видно из кода выше, Circe из коробки предлагает нам готовый вариант решения нашей задачи по конвертации класса в JSON. Для того, чтобы превратить наш JSON в строку, достаточно вызвать noSpace, благодаря которому мы преобразуем JSON в обычную строку. Для демонстрации конвертации строки в класс нам понадобится следующий метод: Здесь, мы возьмем готовый вариант библиотеки без каких-либо изменений. Наши функции выглядят довольно просто, но есть одно «но»: для того, чтобы они заработали, нам необходимо создать специальные конверторы. Создадим пару кейс-классов, которые могли бы использоваться для простейшего обмена информацией: Именно на этих классах мы и будем пробовать различные виды конвертации. Для того, чтобы продемонстрировать работу ручной конвертации, создадим два трейта, один из которых будет отвечать за превращение нашего кейс-класса в строку (ManualEncoders), а другой –за превращение строки в кейс-класс (ManualDecoders). Далее оба этих трейта объединим в один ManualConverter. ManualDecoders будет выглядеть следующим образом: Давайте немного поговорим о том, что же здесь происходит. Мы создаём имплиситы для каждого кейс-класса, которые будут подтягиваться в наш convertToObj, чтобы он понимал – как именно конвертировать эти объекты. Для этого нам необходимо задействовать объект типа Hcurcor. Если представить процесс его работы, то Hcursor представляет собой некую точку, которая может ходить по-нашему JSON-объекту как по дереву. После того, как она пробежалась по нему, создаются взаимосвязи в виде ключ-значение. Тем самым Hcursor может помочь нам извлечь необходимую информацию. По сути, когда мы говорим о Circe, любой процесс имеет этот курсор, просто ранее он был скрыт под верхнеуровневым API. Но при создании декодера нам потребуется спустится на уровень ниже, где он нам и понадобится. После создания курсора, используя for-comprehension, мы начнём создавать наш объект. Просим выдать некие значения по определенным ключам, которые были найдены после просмотра дерева Hcursor и превратить полученное значение в необходимый нам тип данных. Получив все значения, создаём из них необходимый нам класс. Наш ручной Decoder готов и мы можем двигаться дальше. Теперь создадим ручной Encoder. Он будет выглядеть следующим образом: Здесь мы снова создаем имплиситы, которые будут подтягиваться в наш convertToJsonString. Взяв наш класс PersonInfo и обозначив его info, начнём создавать наш JSON-объект. Этот метод принимает tuples вида (String, Json), таким образом мы создаём ключи в нашем JSON-объекте из строк в левой части, а их значения получаем из полей объектов кейс-классов, приведенных к JSON в правой части. Наш ручной Encoder также готов. Таким образом, все готово для ручного конвертера. Теперь объединим оба наших трейта в готовый конвертер для классов Actor, PersonalInfo и можем использовать его там, где он понадобится: Запустим приложение, чтобы убедится, что все работает: Проверим Encoding: Проверим Decoding: Как видно из результатов, все работает корректно. На этом с ручной конвертацией разобрались, теперь можем переходить к полуавтоматической. Ручная конвертация требует написания большого количества шаблонного кода и для того, чтобы избежать этого, Сirce предлагает полуавтоматическую конвертацию. Нам снова понадобится три трейта, но выглядеть они будут куда проще. Полуавтоматический декодер выглядит так: Здесь всего пару коротких имплиситов для классов, которые нам необходимо конвертировать, но присутствует специальный вид декодера, который нам необходимо импортировать: Наш полуавтоматический декодер готов. Давайте взглянем на Encoder: И его импорты: Здесь все то же самое, что и в декодере, только происходит обратный процесс. Наш полуавтоматический энкодер тоже готов. Для полуавтоматического конвертера все готово, остаётся снова объединить оба наших трейта в готовый конвертер: После чего нужно заменить наш ручной конвертер на полуавтоматический: Ну, и запустить то же самое приложение, в котором мы использовали ручной конвертер. Мы получим абсолютно такой же результат: Также в Circe при создании конвертеров в полуавтоматическом режиме есть возможность не писать отдельно энкодер и декодер: достаточно сделать общий Codec, который представляет собой объединенные вместе энкодер и декодер. Выглядит он так: Благодаря чему мы можем не создавать общий трейт для объединения энкодера и декодера, а сразу использовать его, потому что он подходит как для операции превращения классов в JSON, так и обратно. На этом с полуавтоматической конвертацией мы разобрались и нам остается последний вид конвертации, о котором мы поговорим далее. Предыдущий вид конвертации был достаточно прост, но Circe может упростить этот процесс еще больше. Для работы автоматической конвертации нам не потребуется создавать каких-либо трейтов с имплиситами, а лишь потребуется одна маленькая деталь. Именно там, где нужно применять метод конвертации, нам нужно импортировать следующее: Благодаря этому импорту Circe в автоматическом режиме генерирует все, что необходимо для конвертации. Уберем все наши ранее используемые трейты и оставим только один импорт автоматического генератора: Попробуем запустить наше приложение и снова убедимся в том, что все отработало как полагается: Как можно заметить, все на месте и ничего не отличается от вышеиспользуемых конверторов. Какую конвертацию выбирать – дело за вами. Все они работают, вопрос лишь в том – как вы планируете использовать конвертор для решения своих задач.