Использование Jackson #

Васильев Андрей Михайлович, 2024

Версии презентации

Библиотека Jackson #

Представляет собой набор инструментов для обработки данных на JVM-платформе

  • Позволяет считывать и записывать данные в JSON-формате
  • Предоставляет средства для удобной работы с объектами классов
  • Содержит средства для работы с другими форматами данных: BSON, CSV, Protobuf, TOML, YAML, XML и т.д.
  • Является именно набором связных и бинарно совместимых инструментов, а не единым большим проектом
  • Поставляется под лицензией Apache 2.0, разрешающей коммерческое использование

Полезные ссылки:

Потоковая обработка #

Основой для эффективной обработки JSON-документов является ядро библиотеки, которое предоставляет средства для потоковой обработки данных

  • Обработка документа выполняется за один проход
  • Используется минимальный объём памяти

Подключение библиотеки #

В список зависимостей приложения необходимо добавить Maven-артефакт:

"com.fasterxml.jackson.core:jackson-core:2.17.2"

Документацию по классам библиотеки можно посмотреть https://javadoc.io/doc/com.fasterxml.jackson.core/jackson-core/2.17.2/index.html

Ключевые объекты #

diagram

  • JsonFactoryBuilder — строитель фабрики ключевых объектов
  • JsonFactory — фабрика объектов для считывания или записи
  • JsonGenerator — генератор JSON, создаётся отдельно для каждого документа
  • JsonParser — считыватель JSON, создаётся отдельно для каждого документа

С помощью JsonFactoryBuilder можно настроить общие параметры путём включения и выключения

  • JacksonReadFeature, опций по считыванию данных
  • JacksonWriteFeature, опций по записи данных

Создание генератора JSON #

Фабрика JsonFactory предоставляет ряд методов для создания генератора, который сможет записать свои данные: в выходной поток, файл, объект приёма данных

Помимо целевого объекта этим методам также можно передать кодировку данных

Для создания генератора, записывающего данные на стандартный поток вывода достаточно:

val factory: JsonFactory = JsonFactoryBuilder().build()
val outputGenerator: JsonGenerator = factory.createGenerator(System.out)
outputGenerator.prettyPrinter = DefaultPrettyPrinter()

По окончании формировании JSON-документа необходимо вызвать метод close()

Потоковая запись JSON #

При формировании JSON-документа необходимо последовательно описывать состояние целевого JSON-документа, включая все его компоненты

  • Символ начала массива, writeStartArray()
  • Символ окончания массива, writeEndArray()
  • Символ начала объекта, writeStartObject()
  • Символ окончания объекта, writeStartObject()
  • Название поля, writeFieldName(name)
  • Строковое значение, writeString(value)
  • Числовое значение, writeNumber(value)
  • Логическое значение, writeBoolean(value)
  • Null-значение, writeNull()
  • Удобные методы для записи пары ключ-значение, writeNumberField(name, value)

Пример записи простого объекта #

Создадим следующий JSON-документ с потоковой записью данных

{
  "sides" : [ 5, 3, 4 ],
  "color" : "RED",
  "cool" : true
}
with(outputGenerator) {
    writeStartObject()
    writeFieldName("sides")
    writeStartArray()
    writeNumber(5)
    writeNumber(3)
    writeNumber(4)
    writeEndArray()
    writeFieldName("color")
    writeString("RED")
    writeBooleanField("cool", true)
    writeEndObject()
    close()
}

Объектное представление #

Потоковая запись данных является самым быстрым и эффективным способом формирования JSON-документов

Однако каждый документ можно соотнести к некоторой сложной структурой данных внутри приложения, которая включает в себя множество сложных полей и их значений

Модуль jackson-databind предоставляет соответствующие средства, позволяющие решить данную задачу. Данный модуль рассчитан на использование с ЯП Java

Для поддержки данной функциональности в Kotlin на платформе JVM используйте модуль jackson-module-kotlin:

com.fasterxml.jackson.module:jackson-module-kotlin:2.17.+

diagram

Средство отображения объектов #

Для выполнения преобразования Kotlin-объекта в JSON-документ и наоборот необходимо создать компонент отображения объектов, ObjectMapper:

import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
val mapper = jacksonObjectMapper()

Для работы также необходимо определить класс данных Kotlin, который будет формировать данные

data class Triangle(val sideA: Double, val sideB: Double, val sideC: Double)

Создадим на основе объекта данного класса соответствующий JSON-документ:

val triangle = Triangle(3.0, 4.0, 5.0)
mapper.writeValue(System.out, triangle)

Получим следующий вывод:

{"sideA":3.0,"sideB":4.0,"sideC":5.0}

Отображение Kotlin в JSON #

Компонент отображения объектов использует рефлексию, чтобы получить список свойств объекта, их типы и значения

  • Если тип данных есть одновременно и в Kotlin, и в JSON, то при выводе он будет использован (null, логический тип, число, строка)
  • Сложные объекты будут преобразованы в строковое представление
  • Значением ключа будет являться название свойства преобразуемого объекта

Можно использовать аннотации для настройки поведения компонента отображения

Список доступных аннотаций и их воздействие на отображение данных можно прочитать в https://javadoc.io/doc/com.fasterxml.jackson.core/jackson-annotations/2.17.2/index.html

Также стоит отметить аннотации для поддержки классов описания дат https://github.com/FasterXML/jackson-modules-java8

Переименовывание свойств #

data class Pseudo(
    @JsonProperty("Money")
    val info: Int,
)

val mapper = jacksonObjectMapper()
mapper.enable(SerializationFeature.INDENT_OUTPUT)
println(mapper.writeValueAsString(Pseudo(10)))

Результат работы кода:

{
  "Money" : 10
}

Какой интерфейс использовать #

  • Низкоуровневый интерфейс предоставляет самый эффективный способ по преобразованию данных в рамках данного набора библиотек
  • Низкоуровневый интерфейс требует написания кода, отсутствует поведение «по умолчанию»
  • Компонент преобразования данных использует рефлексию, т.е. каждый раз тратится время на анализ структуры преобразуемого объекта
  • Для модификации поведения необходимо разбираться в доступных аннотациях
  • Аннотации могут предоставляться в отдельных библиотеках и разработаны самостоятельно
  • Для написания аннотаций надо понимать структуру низкоуровневого интерфейса
  • Невозможно сделать два представления для одного объекта с помощью компонента преобразования данных
  • Операции преобразования из JSON в объекты Kotlin значительно сложнее при использовании низкоуровневого интерфейса