W3docs

Java JSON с Jackson

Разбор, генерация и привязка JSON в Java с помощью библиотеки Jackson — ObjectMapper и привязка данных.

Jackson — де-факто стандартная JSON-библиотека для Java. В JDK нет собственного JSON API, поэтому почти каждое приложение на Spring, Quarkus или Micronaut использует Jackson для преобразования Java-объектов в JSON-текст и обратно. Точкой входа служит единственный универсальный класс — ObjectMapper — который выполняет две задачи, необходимые всегда: сериализацию (Java-объект → JSON) и десериализацию (JSON → Java-объект).

Если вы только начинаете работать с JSON в Java, начните с введения в JSON; для более лёгкой альтернативной библиотеки смотрите главу о Gson. На этой странице рассматриваются: подключение Jackson, три уровня его API, привязка данных, древовидная модель и аннотации, управляющие маппингом.

Добавление Jackson в проект

Jackson не входит в JDK, поэтому его нужно объявить как зависимость. Артефакт jackson-databind транзитивно подтягивает два других базовых модуля (jackson-core и jackson-annotations).

<!-- Maven -->
<dependency>
  <groupId>com.fasterxml.jackson.core</groupId>
  <artifactId>jackson-databind</artifactId>
  <version>2.17.1</version>
</dependency>
// Gradle
implementation 'com.fasterxml.jackson.core:jackson-databind:2.17.1'

Три способа работы с JSON

Jackson предоставляет доступ к одним и тем же данным тремя разными способами. Выбирайте уровень, подходящий для задачи:

ПодходОсновной типИспользуется когда
Привязка данныхObjectMapper.readValue / writeValueЕсть POJO или record, отражающий структуру JSON — наиболее распространённый случай
Древовидная модельJsonNode через readTreeСтруктура динамическая или нужны лишь несколько полей
ПотоковаяJsonParser / JsonGeneratorОгромные документы, которые нельзя целиком держать в памяти

Привязка данных используется в 90% случаев; остальные два подхода — запасные варианты.

Привязка данных: объекты на входе, JSON на выходе

ObjectMapper.writeValueAsString сериализует любой Java-объект, читая его геттеры (или компоненты record); readValue выполняет обратную операцию, сопоставляя имена полей JSON с полями вашего класса. record — наиболее чистая цель: Jackson 2.12+ автоматически привязывается к его каноническому конструктору, поэтому геттеры и конструктор без аргументов не нужны.

import com.fasterxml.jackson.databind.ObjectMapper;

record User(String name, int age, boolean active) {}

ObjectMapper mapper = new ObjectMapper();

// serialize: Java -> JSON
User ada = new User("Ada", 36, true);
String json = mapper.writeValueAsString(ada);
// {"name":"Ada","age":36,"active":true}

// deserialize: JSON -> Java
User back = mapper.readValue(json, User.class);
System.out.println(back.name()); // Ada

Для коллекций и обобщённых типов передайте Jackson TypeReference, чтобы тип элемента не стёрся при erasure:

import com.fasterxml.jackson.core.type.TypeReference;

List<User> users = mapper.readValue(jsonArray, new TypeReference<List<User>>() {});

Древовидная модель: когда структура неизвестна

Если у вас нет (или вы не хотите создавать) соответствующего класса, разберите JSON в обобщённое дерево JsonNode и перемещайтесь по нему по ключам. Это похоже на ручной разбор в Map/List, но с типизированными методами доступа, такими как asInt() и asText().

import com.fasterxml.jackson.databind.JsonNode;

JsonNode root = mapper.readTree(json);
String name = root.get("name").asText();
int age     = root.get("age").asInt();
JsonNode first = root.get("languages").get(0); // array access by index

Управление маппингом с помощью аннотаций

Имена полей JSON редко точно совпадают с соглашениями Java. Несколько аннотаций устраняют этот разрыв без изменения имён полей:

АннотацияЭффект
@JsonProperty("user_name")Сопоставить поле с другим ключом JSON
@JsonIgnoreИсключить поле в обоих направлениях
@JsonInclude(NON_NULL)Убрать null-поля из вывода
@JsonCreator / @JsonFormatПользовательское создание / форматирование дат
record Account(
    @JsonProperty("user_name") String userName,
    @JsonIgnore String passwordHash) {}

Полный пример: сериализация, разбор и ручная привязка

Jackson недоступен в classpath этого исполнителя, поэтому программа ниже реализует те же концепции — генератор JSON, рекурсивно-нисходящий парсер в дерево Map/List и привязку в record — используя только java.util. Это именно то, что ObjectMapper делает за вас под капотом: обходит граф объектов для генерации текста и токенизирует текст для восстановления дерева.

java— editable, runs on the server

Что следует вынести из этого запуска:

  • Сериализация обходит граф объектов и генерирует текст. Строка serialized показывает вложенный Map/List, преобразованный в {"name":"Ada",...,"languages":["Java","Ada"]} — массив внутри объекта. ObjectMapper.writeValueAsString выполняет такой же рекурсивный обход геттеров POJO или компонентов record.
  • Разбор сначала восстанавливает обобщённое дерево. Тип значения во время выполнения — LinkedHashMap, а не User — это в точности древовидная модель Jackson, где readTree возвращает JsonNode, по которому можно перемещаться по ключу до того, как в игру вступает какой-либо класс.
  • Числа JSON становятся числами Java с выбором типа. Поле age вернулось как Integer (42), потому что в тексте не было десятичной точки; парсер выбрал Integer вместо Double. Jackson принимает идентичное решение, поэтому поле int привязывается чисто, а 3.14 попало бы как Double.
  • Доступ к полям осуществляется по имени, и порядок сохраняется. Использование LinkedHashMap сохранило ключи в порядке вставки, так что name возвращается раньше age. Jackson таким же образом сохраняет порядок ключей объекта, поэтому JSON после «туда-обратно» выглядит как оригинал.
  • Цикл «туда-обратно» не теряет данные, если модель соответствует. Последняя строка повторно сериализовала разобранное дерево в тот же JSON, из которого оно было получено, а типизированный record User правильно привязал name, age и languages — в этом и есть весь смысл привязки данных: текст на входе, объект на выходе, снова текст, без потерь.

Практика

Практика
С помощью Jackson вы получаете JSON-ответ, структуру которого не контролируете, и вам нужно прочитать лишь два поля из большого, глубоко вложенного документа. Какой подход подходит лучше всего?
С помощью Jackson вы получаете JSON-ответ, структуру которого не контролируете, и вам нужно прочитать лишь два поля из большого, глубоко вложенного документа. Какой подход подходит лучше всего?
Was this page helpful?