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-классов (до последних версий)
- Ограниченная поддержка новых дата-тайп API (например, LocalDate, LocalDateTime — нужны кастомные адаптеры)
- Не умеет работать с аннотациями Jackson
- Нет поддержки сложных полиморфных структур «из коробки»
- Нет автоматической поддержки bidirectional-ссылок (цикличность)
Кастомные адаптеры (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 |
|---|---|---|
| Простота использования | +++++ (очень просто) | +++ (чуть сложнее) |
| Размер библиотеки | Маленький | Крупнее |
| Скорость | Быстро, но чуть медленнее | Очень быстро |
| Гибкость | Средняя | Высокая (больше настроек) |
| Поддержка аннотаций | Свои (@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.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ