Avatar ·

Jackson: как десериализовать JSON-массив объектов в Java

Всем привет. Я только начал изучать Java и Jackson, и у меня возникла проблема. У меня есть JSON-строка, которая представляет собой массив объектов. Например, вот такой JSON:
[
  {"name": "Alice", "age": 30},
  {"name": "Bob", "age": 25},
  {"name": "Charlie", "age": 35}
]
Я пытаюсь прочитать этот JSON и превратить его в список объектов моего класса `Person`. Мой класс выглядит так:
public class Person {
    private String name;
    private int age;

    // Конструктор по умолчанию
    public Person() {}

    // Геттеры и сеттеры
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    public int getAge() { return age; }
    public void setAge(int age) { this.age = age; }
}
Я пробую такой код:
ObjectMapper mapper = new ObjectMapper();
String json = "[{\"name\":\"Alice\",\"age\":30},{\"name\":\"Bob\",\"age\":25}]";
Person person = mapper.readValue(json, Person.class);
Но получаю исключение: `com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize value of type 'Person' from Array value (token 'JsonToken.START_ARRAY')`. Я понимаю, что Jackson ожидает один объект, а получает массив. Как правильно десериализовать массив объектов? Подскажите, пожалуйста, как это сделать. Заранее спасибо! <<>>

Проблема и её решение

Вы столкнулись с типичной ситуацией: Jackson ожидает один JSON-объект (например, {"name":"Alice","age":30}), а вы передаёте массив объектов. Чтобы десериализовать массив, нужно явно указать тип коллекции. Вот несколько способов.

Способ 1: Использование TypeReference (рекомендуется)

Самый гибкий и безопасный способ — использовать TypeReference. Он позволяет Jackson корректно определить generic-тип:

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.List;

ObjectMapper mapper = new ObjectMapper();
String json = "[{\"name\":\"Alice\",\"age\":30},{\"name\":\"Bob\",\"age\":25}]";

List<Person> people = mapper.readValue(json, new TypeReference<List<Person>>() {});
System.out.println(people); // [Person{name='Alice', age=30}, Person{name='Bob', age=25}]

Почему это работает? TypeReference сохраняет информацию о generic-типе во время выполнения, что необходимо из-за стирания типов (type erasure) в Java. Без него Jackson не знает, что внутри List находятся объекты Person.

Способ 2: Использование массива классов

Можно десериализовать в массив Person[], а затем преобразовать в список:

Person[] peopleArray = mapper.readValue(json, Person[].class);
List<Person> people = Arrays.asList(peopleArray);

Этот способ проще, но возвращает массив фиксированной длины. Если вам нужен изменяемый список, оберните результат в new ArrayList<>(Arrays.asList(...)).

Способ 3: Использование CollectionType (для продвинутых)

Можно использовать фабрику типов Jackson:

JavaType type = mapper.getTypeFactory().constructCollectionType(List.class, Person.class);
List<Person> people = mapper.readValue(json, type);

Этот подход даёт полный контроль и полезен, когда тип коллекции динамический.

Важные моменты

  • Конструктор по умолчанию: Убедитесь, что у класса Person есть конструктор без параметров (или используйте аннотацию @JsonCreator).
  • Геттеры/сеттеры: Jackson использует их для заполнения полей. Если их нет, добавьте аннотации @JsonProperty или настройте видимость через ObjectMapper.
  • Обработка ошибок: Всегда оборачивайте вызов в try-catch или пробрасывайте исключение.

Полный рабочий пример

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.List;

public class Main {
    public static void main(String[] args) throws Exception {
        String json = "[{\"name\":\"Alice\",\"age\":30},{\"name\":\"Bob\",\"age\":25}]";
        ObjectMapper mapper = new ObjectMapper();
        List<Person> people = mapper.readValue(json, new TypeReference<List<Person>>() {});
        people.forEach(p -> System.out.println(p.getName() + " - " + p.getAge()));
    }
}

Вывод: Alice - 30 и Bob - 25.

Используйте TypeReference как основной паттерн — это чисто, безопасно и работает с любыми вложенными generic-типами (например, Map<String, List<Person>>).

<<>>

Альтернативный подход: десериализация в массив с последующей конвертацией

Если вы не хотите использовать TypeReference или работать с коллекциями напрямую, можно десериализовать JSON в массив объектов, а затем преобразовать его в список. Это может быть удобно, когда вам нужно быстро протестировать код или вы работаете с фиксированным количеством элементов.

ObjectMapper mapper = new ObjectMapper();
String json = "[{\"name\":\"Alice\",\"age\":30},{\"name\":\"Bob\",\"age\":25}]";

Person[] peopleArray = mapper.readValue(json, Person[].class);
// Если нужен список:
List<Person> peopleList = new ArrayList<>(Arrays.asList(peopleArray));

Плюсы:

  • Простота и читаемость кода.
  • Не требует импорта TypeReference.

Минусы:

  • Массив фиксированной длины (если нужен изменяемый список, требуется дополнительная обёртка).
  • Менее гибкий для сложных вложенных структур.

Этот способ хорошо подходит для простых случаев, когда вы точно знаете, что JSON — это плоский массив объектов. Для более сложных сценариев (например, вложенные generic-типы) лучше использовать TypeReference.