JavaRush /Курсы /JAVA 25 SELF /Jackson — чтение и запись JSON, аннотации

Jackson — чтение и запись JSON, аннотации

JAVA 25 SELF
46 уровень , 1 лекция
Открыта

1. Введение в Jackson

Обмен данными между Java-приложением и внешним миром очень часто можно представить как такую задачу: «Как превратить объект Java в JSON — и обратно?» Можно, конечно, написать свой парсер на String.split и регулярках (и даже получить удовольствие от страданий), но в реальных проектах так никто не делает.

В Java для работы с JSON есть несколько популярных библиотек. Главная из них — Jackson. Она настолько популярна, что входит в состав Spring Boot и используется во множестве других фреймворков и библиотек.

Что такое Jackson?

Jackson — это мощная и гибкая библиотека для сериализации (преобразования Java-объекта в JSON) и десериализации (обратная операция). Она состоит из нескольких модулей, но для 90% задач вам понадобятся два:

  • jackson-core — ядро, низкоуровневый парсер.
  • jackson-databind — высокоуровневый модуль, который умеет превращать Java-объекты в JSON и обратно.

Всё, что вам нужно знать для начала: если вы видите класс ObjectMapper — это Jackson.

Jackson считают стандартом де-факто для работы с JSON в Java, потому что он сочетает простоту и мощь. Чтобы сериализовать или десериализовать данные, достаточно буквально пары строк кода, и это делает библиотеку удобной даже для новичков. При этом она не ограничивается базовыми возможностями: благодаря аннотациям и множеству настроек можно гибко управлять тем, как именно данные будут превращаться в объекты и обратно, будь то коллекции, вложенные сущности или даты в разных форматах.

Немаловажно и то, что Jackson работает очень быстро и экономно, что критично для реальных проектов с большим количеством данных. Разработчики библиотеки поддерживают новые версии Java и актуальные изменения в стандарте JSON — Jackson остаётся надёжным выбором как для простых приложений, так и для крупных корпоративных систем.

2. Чтение JSON (десериализация)

Давайте попробуем прочитать JSON-строку и превратить её в Java-объект. Для этого нам понадобятся:

  • Класс данных (например, Person)
  • Класс ObjectMapper из Jackson

Подключение Jackson

Если вы используете Maven, добавьте в pom.xml:

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.17.0</version>
</dependency>

Если Gradle — аналогично:

implementation 'com.fasterxml.jackson.core:jackson-databind:2.17.0'

Пример класса

public class Person {
    public String name;
    public int age;
}

Примечание: Для простоты поля сделаны публичными. Позже мы обсудим работу с приватными полями и геттерами/сеттерами.

Пример JSON

{
  "name": "Alice",
  "age": 30
}

Десериализация: превращаем JSON в объект

import com.fasterxml.jackson.databind.ObjectMapper;

public class Main {
    public static void main(String[] args) throws Exception {
        String json = "{\"name\": \"Alice\", \"age\": 30}";

        ObjectMapper mapper = new ObjectMapper();
        Person person = mapper.readValue(json, Person.class);

        System.out.println(person.name); // Alice
        System.out.println(person.age);  // 30
    }
}

Здесь Jackson парсит JSON-строку, находит поля, совпадающие по имени с полями или геттерами класса, и заполняет объект соответствующими значениями с помощью readValue.

Десериализация списка объектов

Допустим, у нас есть массив:

[
  { "name": "Bob", "age": 22 },
  { "name": "Eve", "age": 27 }
]

Десериализуем в список:

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

String json = "[{\"name\": \"Bob\", \"age\": 22}, {\"name\": \"Eve\", \"age\": 27}]";
ObjectMapper mapper = new ObjectMapper();

List<Person> people = mapper.readValue(json, new TypeReference<List<Person>>() {});

for (Person p : people) {
    System.out.println(p.name + " (" + p.age + ")");
}

Возможно, у вас возник вопрос: а почему нельзя просто написать mapper.readValue(json, List.class)? Помните, мы недавно выяснили, что дженерики в Java стираются во время компиляции? Нам поэтому нужен TypeReference, чтобы Jackson понял, что внутри списка должны быть объекты типа Person.

3. Запись JSON (сериализация)

Теперь давайте проделаем обратную операцию: превратим Java-объект в JSON-строку.

Пример

ObjectMapper mapper = new ObjectMapper();

Person person = new Person();
person.name = "Charlie";
person.age = 40;

String json = mapper.writeValueAsString(person);
System.out.println(json);
// {"name":"Charlie","age":40}

Сериализация списка объектов

ObjectMapper mapper = new ObjectMapper();

List<Person> people = new ArrayList<>();
people.add(new Person("Anna", 25));
people.add(new Person("Dmitry", 31));

String json = mapper.writeValueAsString(people);
System.out.println(json);
// [{"name":"Anna","age":25},{"name":"Dmitry","age":31}]

Запись в файл

ObjectMapper mapper = new ObjectMapper();

Person person = new Person();
person.name = "Charlie";
person.age = 40;

mapper.writeValue(new File("person.json"), person);
// Файл person.json теперь содержит JSON-объект

Красивый (pretty) JSON

По умолчанию Jackson пишет всё в одну строку. Но есть вариант и более «человекоориентированного» написания — pretty printing. Это значит, что JSON-строка выводится в читаемом виде: с отступами, переносами строк и аккуратным форматированием.

В отличие от «обычного» JSON, который чаще всего пишется в одну строку ради компактности, «красивый» вариант нужен для людей — чтобы легко просматривать структуру данных в логах, файлах или на экране.

ObjectMapper mapper = new ObjectMapper();

Person person = new Person();
person.name = "Charlie";
person.age = 40;

String prettyJson = mapper.writerWithDefaultPrettyPrinter()
                          .writeValueAsString(person);

System.out.println(prettyJson);
/*
{
  "name" : "Charlie",
  "age" : 40
}
*/

4. Аннотации Jackson

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

@JsonProperty

Позволяет указать имя поля в JSON, если оно отличается от имени поля в классе.

import com.fasterxml.jackson.annotation.JsonProperty;

public class Person {
    @JsonProperty("full_name")
    public String name;

    public int age;
}
{"full_name": "Olga", "age": 28}

Jackson поймёт, что поле full_name из JSON нужно записать в поле name объекта.

@JsonIgnore

Если вы не хотите сериализовать или десериализовать какое-то поле:

import com.fasterxml.jackson.annotation.JsonIgnore;

public class Person {
    public String name;

    @JsonIgnore
    public int age;
}

В JSON не будет поля age, даже если оно есть в объекте.

@JsonInclude

Управляет тем, какие поля попадут в JSON. Например, сериализовать только непустые поля:

import com.fasterxml.jackson.annotation.JsonInclude;

@JsonInclude(JsonInclude.Include.NON_NULL)
public class Person {
    public String name;
    public Integer age;
}

Если age == null, в JSON не будет ключа "age".

@JsonFormat

Позволяет задать формат для сериализации дат и времени.

import com.fasterxml.jackson.annotation.JsonFormat;
import java.util.Date;

public class Event {
    public String title;

    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss")
    public Date date;
}
Event event = new Event();
event.title = "Hackathon";
event.date = new Date();

ObjectMapper mapper = new ObjectMapper();
String json = mapper.writeValueAsString(event);
// {"title":"Hackathon","date":"2024-06-07 15:23:00"}

Пример: всё вместе

import com.fasterxml.jackson.annotation.*;

@JsonInclude(JsonInclude.Include.NON_NULL)
public class Person {
    @JsonProperty("full_name")
    private String name;

    private int age;

    @JsonIgnore
    private String password;

    // Геттеры и сеттеры обязательны для приватных полей!
    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; }
}

5. Практика: сериализация и десериализация с учётом аннотаций

Давайте расширим ваше учебное приложение — теперь у нас есть класс User с приватными полями, датой регистрации и паролем, который не должен попадать в JSON.

import com.fasterxml.jackson.annotation.*;
import java.util.Date;

@JsonInclude(JsonInclude.Include.NON_NULL)
public class User {
    @JsonProperty("login")
    private String username;

    private int age;

    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd")
    private Date registered;

    @JsonIgnore
    private String password;

    // Геттеры и сеттеры обязательны!
    public String getUsername() { return username; }
    public void setUsername(String username) { this.username = username; }

    public int getAge() { return age; }
    public void setAge(int age) { this.age = age; }

    public Date getRegistered() { return registered; }
    public void setRegistered(Date registered) { this.registered = registered; }

    public String getPassword() { return password; }
    public void setPassword(String password) { this.password = password; }
}

Сериализация

import com.fasterxml.jackson.databind.ObjectMapper;
import java.text.SimpleDateFormat;

ObjectMapper mapper = new ObjectMapper();

User user = new User();
user.setUsername("superuser");
user.setAge(42);
user.setRegistered(new SimpleDateFormat("yyyy-MM-dd").parse("2024-06-07"));
user.setPassword("qwerty123"); // Не будет в JSON!

String json = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(user);

System.out.println(json);
/*
{
  "login" : "superuser",
  "age" : 42,
  "registered" : "2024-06-07"
}
*/

Десериализация

import com.fasterxml.jackson.databind.ObjectMapper;

ObjectMapper mapper = new ObjectMapper();

String json = "{ \"login\": \"superuser\", \"age\": 42, \"registered\": \"2024-06-07\" }";
User user = mapper.readValue(json, User.class);

System.out.println(user.getUsername()); // superuser
System.out.println(user.getAge());      // 42
System.out.println(user.getRegistered());// Fri Jun 07 00:00:00 ...
System.out.println(user.getPassword()); // null (и это хорошо!)

6. Типичные ошибки при работе с Jackson

Ошибка №1: Отсутствует конструктор без параметров.
Jackson не сможет создать объект класса, если у него нет конструктора без параметров. Часто это бывает в классах, где явно объявлен только конструктор с аргументами.

Ошибка №2: Приватные поля без геттеров/сеттеров.
Если вы сделали все поля приватными, но забыли добавить геттеры и сеттеры, Jackson не сможет их заполнить при десериализации (по умолчанию).

Ошибка №3: Несовпадение имён полей.
Когда имя поля в JSON отличается от имени поля/геттера в Java, Jackson не найдёт соответствия. Используйте @JsonProperty.

Ошибка №4: Не тот формат даты.
Если формат даты в JSON не совпадает с ожидаемым форматом Java, Jackson выдаст ошибку парсинга. Используйте @JsonFormat для настройки.

Ошибка №5: Попытка сериализовать поля с аннотацией @JsonIgnore.
Такие поля не попадут в JSON — это не баг, а фича.

Ошибка №6: Сериализация/десериализация коллекций без указания типа.
Если не использовать TypeReference, Jackson не поймёт, какого типа объекты внутри коллекции.

Ошибка №7: Исключение при чтении/записи файла.
Не забудьте обрабатывать IOException при работе с файлами.

1
Задача
JAVA 25 SELF, 46 уровень, 1 лекция
Недоступна
Конфиденциальность данных пользователя 🕵️‍♀️
Конфиденциальность данных пользователя 🕵️‍♀️
1
Задача
JAVA 25 SELF, 46 уровень, 1 лекция
Недоступна
Управление событиями в университетском календаре 🗓️
Управление событиями в университетском календаре 🗓️
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ