JavaRush /Курси /JAVA 25 SELF /Gson — серіалізація та десеріалізація, налаштування

Gson — серіалізація та десеріалізація, налаштування

JAVA 25 SELF
Рівень 46 , Лекція 2
Відкрита

1. Вступ до Gson

Ми вже познайомилися з Jackson і побачили, чому він вважається стандартом де-факто для роботи з JSON у Java. Але є й інша бібліотека, яка здобула величезну популярність, особливо у світі Android, — це Gson. Gson створювався в Google як легке й просте рішення для серіалізації та десеріалізації об’єктів Java у JSON. Його цінують за мінімальний поріг входу: щоб почати працювати, майже нічого не потрібно налаштовувати — більшість завдань вирішується буквально «з коробки».

Ще одна перевага Gson полягає в його легкості. Бібліотека займає мало місця і не тягне за собою безліч залежностей, тому її часто використовують там, де критичний розмір застосунку, наприклад, на мобільних пристроях. Gson став фактичним стандартом для Android‑проєктів — компактність і простота відіграють там вирішальну роль.

До речі, назва Gson розшифровується як Google JSON. Іноді в спільноті можна зустріти жартівливу розшифровку — Genius’ Son («син генія»), але це, звісно, неофіційно. Просто гра слів.

Підключення Gson до проєкту

Якщо ви використовуєте Maven або Gradle, просто додайте залежність (версія може відрізнятися):

Maven:

<dependency>
    <groupId>com.google.code.gson</groupId>
    <artifactId>gson</artifactId>
    <version>2.10.1</version>
</dependency>

Gradle:

implementation 'com.google.code.gson:gson:2.10.1'

Ми поки що не вивчали збирання проєктів, тому для початку можна просто завантажити jar-файл з офіційної сторінки Gson і додати його у проєкт.

2. Базові операції: серіалізація та десеріалізація

Подивімося, як серіалізувати й десеріалізувати об’єкти за допомогою Gson на прикладі простого класу.

Приклад: клас User

// Клас для прикладів
public class User {
    private String name;
    private int age;
    private boolean active;

    // Конструктор
    public User(String name, int age, boolean active) {
        this.name = name;
        this.age = age;
        this.active = active;
    }

    // Гетери й сетери (Gson використовує їх за потреби)
    public String getName() { return name; }
    public int getAge() { return age; }
    public boolean isActive() { return active; }
}

Серіалізація: об’єкт → JSON

import com.google.gson.Gson;

public class GsonExample {
    public static void main(String[] args) {
        User user = new User("Alice", 25, true);

        Gson gson = new Gson();
        String json = gson.toJson(user);

        System.out.println(json);
        // {"name":"Alice","age":25,"active":true}
    }
}

Зверніть увагу: поля серіалізуються з їхніми іменами, заданими в класі!

Десеріалізація: JSON → об’єкт

public class GsonExample {
    public static void main(String[] args) {
        String json = "{\"name\":\"Bob\",\"age\":30,\"active\":false}";

        Gson gson = new Gson();
        User user = gson.fromJson(json, User.class);

        System.out.println(user.getName()); // Bob
        System.out.println(user.getAge());  // 30
        System.out.println(user.isActive());// false
    }
}

Робота зі списками об’єктів

Gson працює з колекціями трохи складніше, ніж Jackson, але усе розв’язується.

import java.util.List;
import java.util.Arrays;
import com.google.gson.reflect.TypeToken;
import java.lang.reflect.Type;

public class GsonListExample {
    public static void main(String[] args) {
        List<User> users = Arrays.asList(
            new User("Alice", 25, true),
            new User("Bob", 30, false)
        );

        Gson gson = new Gson();
        String json = gson.toJson(users);
        System.out.println(json);
        // [{"name":"Alice","age":25,"active":true},{"name":"Bob","age":30,"active":false}]

        // Десеріалізація списку
        Type userListType = new TypeToken<List<User>>(){}.getType();
        List<User> users2 = gson.fromJson(json, userListType);
        System.out.println(users2.get(0).getName()); // Alice
    }
}

Важливий нюанс: для десеріалізації колекцій використовуйте TypeToken<>!

3. Налаштування Gson: GsonBuilder

Gson надає гнучке налаштування через клас GsonBuilder. З його допомогою ви можете ввімкнути pretty printing, серіалізацію значень null, форматування дат і багато іншого.

Приклад: pretty printing і серіалізація null

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;

public class GsonBuilderExample {
    public static void main(String[] args) {
        User user = new User("Charlie", 0, false);

        Gson gson = new GsonBuilder()
            .setPrettyPrinting()        // Гарне виведення (відступи)
            .serializeNulls()           // Серіалізувати null‑поля
            .create();

        String json = gson.toJson(user);
        System.out.println(json);
        /*
        {
          "name": "Charlie",
          "age": 0,
          "active": false
        }
        */
    }
}

Форматування дат

Якщо у вас є поля типу Date, за замовчуванням Gson серіалізує їх у специфічному форматі. Можна задати власний формат:

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import java.util.Date;

public class DateExample {
    private String event;
    private Date date;

    public DateExample(String event, Date date) {
        this.event = event;
        this.date = date;
    }
}

public class Main {
    public static void main(String[] args) {
        DateExample meeting = new DateExample("Team Meeting", new Date());

        Gson gson = new GsonBuilder()
            .setDateFormat("yyyy-MM-dd HH:mm:ss")
            .create();

        String json = gson.toJson(meeting);
        System.out.println(json);
        // {"event":"Team Meeting","date":"2024-06-10 13:45:23"}
    }
}

4. Анотації Gson: керування серіалізацією

Gson підтримує анотації для точнішого контролю над серіалізацією та десеріалізацією.

@SerializedName — перейменування поля

Якщо ви хочете, щоб поле в JSON називалося інакше, використовуйте @SerializedName:

import com.google.gson.annotations.SerializedName;

public class User {
    @SerializedName("full_name")
    private String name;
    private int age;
    private boolean active;

    public User(String name, int age, boolean active) {
        this.name = name;
        this.age = age;
        this.active = active;
    }
}

Тепер під час серіалізації поле матиме назву full_name:

User user = new User("Diana", 28, true);
String json = new Gson().toJson(user);
// {"full_name":"Diana","age":28,"active":true}

@Expose — серіалізація лише позначених полів

Якщо ви хочете серіалізувати лише певні поля, використовуйте @Expose і налаштуйте Gson:

import com.google.gson.annotations.Expose;

public class User {
    @Expose
    private String name;

    @Expose
    private int age;

    private boolean active; // не серіалізується

    public User(String name, int age, boolean active) {
        this.name = name;
        this.age = age;
        this.active = active;
    }
}

Створюємо Gson з підтримкою @Expose:

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;

Gson gson = new GsonBuilder()
    .excludeFieldsWithoutExposeAnnotation()
    .create();

User user = new User("Eve", 21, false);
String json = gson.toJson(user);
// {"name":"Eve","age":21}

@Since/@Until — умовна серіалізація за версією
Можна використовувати @Since і @Until, щоб серіалізувати поля лише для певних версій (рідко використовується на практиці, але знати корисно).

5. Особливості та обмеження Gson

Робота з вкладеними об’єктами

Gson чудово працює з вкладеними об’єктами:

public class Profile {
    private User user;
    private String bio;

    public Profile(User user, String bio) {
        this.user = user;
        this.bio = bio;
    }
}

Profile profile = new Profile(new User("Frank", 27, true), "Java developer");
String json = new Gson().toJson(profile);
// {"user":{"name":"Frank","age":27,"active":true},"bio":"Java developer"}

Робота з колекціями

З серіалізацією колекцій (List, Map) проблем немає, але для десеріалізації використовуйте TypeToken (див. вище).

Обмеження Gson порівняно з Jackson

  • Немає підтримки Java record-класів (до останніх версій)
  • Обмежена підтримка нового date/time API (наприклад, LocalDate, LocalDateTime — потрібні кастомні адаптери)
  • Не вміє працювати з анотаціями Jackson
  • Немає підтримки складних поліморфних структур «з коробки»
  • Немає автоматичної підтримки двонапрямлених посилань (циклічних посилань)

Кастомні адаптери (TypeAdapter)

Якщо стандартних можливостей замало, можна написати власний адаптер для серіалізації/десеріалізації складних типів.

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.TypeAdapter;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;

import java.io.IOException;

public class BooleanAsIntAdapter extends TypeAdapter<Boolean> {
    @Override
    public void write(JsonWriter out, Boolean value) throws IOException {
        out.value(value ? 1 : 0);
    }

    @Override
    public Boolean read(JsonReader in) throws IOException {
        return in.nextInt() == 1;
    }
}

// Використання:
Gson gson = new GsonBuilder()
    .registerTypeAdapter(Boolean.class, new BooleanAsIntAdapter())
    .create();

6. Практика: серіалізація й десеріалізація з налаштуваннями

Розвиньмо ваш навчальний застосунок і додаймо збереження та завантаження списку користувачів у форматі JSON.

Клас User з анотаціями

import com.google.gson.annotations.SerializedName;
import com.google.gson.annotations.Expose;

public class User {
    @Expose
    @SerializedName("full_name")
    private String name;

    @Expose
    private int age;

    private boolean active; // не серіалізується

    public User(String name, int age, boolean active) {
        this.name = name;
        this.age = age;
        this.active = active;
    }

    // гетери й сетери…
}

Зберігаємо список користувачів у JSON

import java.util.List;
import java.util.Arrays;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;

public class SaveUsers {
    public static void main(String[] args) {
        List<User> users = Arrays.asList(
            new User("Ivan", 23, true),
            new User("Olga", 19, false)
        );

        Gson gson = new GsonBuilder()
            .excludeFieldsWithoutExposeAnnotation()
            .setPrettyPrinting()
            .create();

        String json = gson.toJson(users);
        System.out.println(json);
        /*
        [
          {
            "full_name": "Ivan",
            "age": 23
          },
          {
            "full_name": "Olga",
            "age": 19
          }
        ]
        */
    }
}

Завантажуємо список користувачів із JSON

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.reflect.TypeToken;
import java.lang.reflect.Type;
import java.util.List;

public class LoadUsers {
    public static void main(String[] args) {
        String json = "[{\"full_name\":\"Ivan\",\"age\":23},{\"full_name\":\"Olga\",\"age\":19}]";

        Gson gson = new GsonBuilder()
            .excludeFieldsWithoutExposeAnnotation()
            .create();

        Type userListType = new TypeToken<List<User>>(){}.getType();
        List<User> users = gson.fromJson(json, userListType);

        for (User user : users) {
            System.out.println(user.getName() + " (" + user.getAge() + ")");
        }
        // Ivan (23)
        // Olga (19)
    }
}

7. Порівняння Gson і Jackson

Характеристика Gson Jackson
Простота використання +++++ (дуже просто) +++ (трохи складніше)
Розмір бібліотеки Невеликий Більший
Швидкість Швидко, але трохи повільніше за Jackson Дуже швидко
Гнучкість Середня Висока (більше налаштувань)
Підтримка анотацій Власні (@SerializedName) Власні (@JsonProperty тощо)
Підтримка нових типів Обмежена Відмінна (Java 8+, record)
Підтримка Android Відмінна Хороша, але важча
Робота з датами Лише через адаптери Із коробки
Поліморфізм Обмежений Гнучко налаштовується

8. Типові помилки під час роботи з Gson

Помилка № 1: не використовуєте TypeToken для колекцій.
Якщо десеріалізуєте список або мапу, обов’язково використовуйте TypeToken<>, інакше отримаєте дивні помилки або порожні колекції.

Помилка № 2: немає конструктора без параметрів.
Gson може працювати і без конструктора за замовчуванням, але іноді під час десеріалізації складних об’єктів без такого конструктора можуть виникати помилки. Краще завжди додавати конструктор без параметрів, якщо плануєте десеріалізацію.

Помилка № 3: невідповідність імен полів.
Якщо в JSON поле називається "full_name", а в класі — "name", без анотації @SerializedName("full_name") поле не буде зіставлене, і значення буде null.

Помилка № 4: проблеми з приватними полями.
Gson може серіалізувати приватні поля, але якщо є лише приватні поля й немає гетерів/сетерів, іноді виникають проблеми під час десеріалізації. Краще використовувати гетери й сетери.

Помилка № 5: робота з датами.
За замовчуванням Gson серіалізує Date у незручному форматі. Для LocalDate, LocalDateTime та інших нових типів без кастомних адаптерів будуть помилки серіалізації.

Помилка № 6: не використовуєте @Expose, але увімкнули excludeFieldsWithoutExposeAnnotation().
Якщо ви увімкнули excludeFieldsWithoutExposeAnnotation(), але не позначили поля анотацією @Expose, вони не серіалізуватимуться й не десеріалізуватимуться — результатом буде порожній JSON або об’єкти з null.

Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ