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 делает за вас под капотом: обходит граф объектов для генерации текста и токенизирует текст для восстановления дерева.
Что следует вынести из этого запуска:
- Сериализация обходит граф объектов и генерирует текст. Строка
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— в этом и есть весь смысл привязки данных: текст на входе, объект на выходе, снова текст, без потерь.