Введение: почему каждый Java-разработчик должен знать этот трюк
Представьте: вы получили JSON с сервера, в нём — десятки однотипных объектов. Клиенты, товары, логи — неважно. Вы берёте ObjectMapper, пишете readValue(json, MyClass[].class) — и всё работает. А через месяц код падает с ClassCastException, потому что кто-то заменил массив на List. Знакомо?
Вопрос «How to use Jackson to deserialise an array of objects» на Stack Overflow собрал десятки тысяч просмотров. И это не случайно: десериализация массива объектов в Java — одна из тех задач, которые кажутся простыми, пока не столкнёшься с дженериками и Type Erasure. В 2025 году Jackson остаётся стандартом де-факто для работы с JSON, но лучшие практики изменились. В этой статье мы разберём не только базовые подходы, но и современные техники, которые уберегут вас от головной боли.
Базовый подход: массив или список?
Самый простой способ — использовать массив. Jackson прекрасно понимает, что MyClass[].class — это массив объектов:
ObjectMapper mapper = new ObjectMapper();
String json = "[{\"id\":1,\"name\":\"Alice\"},{\"id\":2,\"name\":\"Bob\"}]";
MyClass[] array = mapper.readValue(json, MyClass[].class);
Работает? Да. Но у этого подхода есть недостатки:
- Массив фиксированного размера — неудобно для динамических данных.
- Нет методов коллекций (
stream(),filter(),remove()). - Сложнее расширять, если позже понадобится
List.
Поэтому в 90% случаев вы захотите получить List<MyClass>. И тут начинается самое интересное — Type Erasure в Java. Если написать:
List<MyClass> list = mapper.readValue(json, List.class); // warning: unchecked
Компилятор будет ругаться, а в рантайме вы получите List<LinkedHashMap>, а не List<MyClass>. Дженерики стираются, и Jackson не знает, во что мапить элементы.
TypeReference: правильный способ для List
Решение — использовать TypeReference. Это анонимный внутренний класс, который сохраняет информацию о дженериках через рефлексию:
List<MyClass> list = mapper.readValue(json, new TypeReference<List<MyClass>>() {});
Обратите внимание на {} — это создаёт анонимный класс, который захватывает тип. Jackson через TypeFactory понимает, что нужно десериализовать List из MyClass. Этот подход — золотой стандарт в 2025 году.
Когда TypeReference неудобен?
- Если вы динамически формируете тип (например, из конфигурации).
- Если нужно много раз десериализовать одно и то же — каждый раз создавать анонимный класс дороговато.
Для этих случаев есть альтернатива — CollectionType.
CollectionType: мощь JavaType
Jackson предоставляет TypeFactory для построения сложных типов:
JavaType type = mapper.getTypeFactory()
.constructCollectionType(List.class, MyClass.class);
List<MyClass> list = mapper.readValue(json, type);
Этот метод:
- Не создаёт анонимные классы (меньше overhead).
- Позволяет конструировать типы динамически:
constructParametricType(Map.class, String.class, MyClass.class). - Идеален для библиотек и фреймворков, где типы неизвестны на этапе компиляции.
Лучшая практика 2025: используйте TypeReference для простых случаев, CollectionType — когда нужна гибкость или производительность.
Reader и InputStream: не забывайте про ресурсы
Часто JSON приходит из файла или сети. Вместо строки передавайте Reader или InputStream — это эффективнее по памяти:
try (InputStream is = getClass().getResourceAsStream("/data.json")) {
List<MyClass> list = mapper.readValue(is, new TypeReference<List<MyClass>>() {});
}
Jackson сам позаботится о кодировке и буферизации. Если вы используете readValue(String, ...), вся строка уже в памяти — для больших массивов это может быть проблемой.
Обработка ошибок: что может пойти не так?
Даже с правильным типом десериализация может упасть. Топ-3 ошибки:
- Неизвестные поля. По умолчанию Jackson игнорирует поля, которых нет в классе. Но если вы включили
FAIL_ON_UNKNOWN_PROPERTIES, любое лишнее поле вызовет исключение. Решение: используйте@JsonIgnoreProperties(ignoreUnknown = true)на уровне класса. - Несоответствие типов. В JSON строка, а в классе —
int. Jackson попытается преобразовать, но может упасть сJsonMappingException. Используйте кастомные десериализаторы или аннотацию@JsonFormat. - Пустой массив.
[]корректно десериализуется в пустой список. Но если JSON отсутствует или равенnull, вы получитеnull. Проверяйте на null после десериализации.
Производительность: Jackson 2.17+ и Afterburner
В 2025 году Jackson версии 2.17+ включает модуль Afterburner, который генерирует байткод для доступа к полям — до 30% быстрее стандартной рефлексии. Подключите его как зависимость:
<dependency>
<groupId>com.fasterxml.jackson.module</groupId>
<artifactId>jackson-module-afterburner</artifactId>
<version>2.17.0</version>
</dependency>
И зарегистрируйте:
ObjectMapper mapper = JsonMapper.builder()
.addModule(new AfterburnerModule())
.build();
Для десериализации массивов объектов этот модуль особенно эффективен — он оптимизирует создание экземпляров и присваивание полей.
Jackson vs другие библиотеки в 2025: что выбрать?
Краткий обзор для контекста:
- Gson — проще, но медленнее. Для массивов объектов нужно использовать
TypeToken— аналогTypeReference. - JSON-B (Jakarta) — стандарт Jakarta EE