Java JSON с Gson
Разбор и сериализация JSON в Java с библиотекой Google Gson — toJson, fromJson и TypeToken.
JSON является общепринятым форматом современных веб-API, и Java-приложениям постоянно нужно преобразовывать объекты в JSON и разбирать JSON обратно в объекты. Gson — это open-source библиотека Google именно для этого: небольшой инструментарий без зависимостей, который отображает Java-объекты на JSON-текст и обратно с минимальным количеством шаблонного кода. В этой главе показано, как работает Gson и какие паттерны вы будете использовать каждый день.
Если вы не знакомы с самим форматом, начните со введения в JSON; для основной альтернативной библиотеки см. Java JSON с Jackson.
Добавление Gson в проект
Gson не входит в состав JDK, поэтому его нужно добавить как зависимость. В Maven он объявляется в pom.xml:
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.11.0</version>
</dependency>В Gradle та же зависимость записывается в одну строку:
implementation 'com.google.code.gson:gson:2.11.0'Всё взаимодействие с Gson происходит через единственный входной класс — Gson. Обычно создают один экземпляр и используют его повторно — он потокобезопасен и не требует значительных ресурсов.
import com.google.gson.Gson;
Gson gson = new Gson();Сериализация объектов в JSON
Преобразование Java-объекта в JSON-текст называется сериализацией, и Gson выполняет её с помощью toJson(). Вы передаёте любой объект, а Gson обходит его поля через рефлексию, формируя JSON-строку. Для обычных классов аннотации и конфигурация не нужны.
class Book {
String title;
String author;
int year;
boolean inStock;
Book(String title, String author, int year, boolean inStock) {
this.title = title;
this.author = author;
this.year = year;
this.inStock = inStock;
}
}
Gson gson = new Gson();
Book b = new Book("Clean Code", "Robert Martin", 2008, true);
String json = gson.toJson(b);
// {"title":"Clean Code","author":"Robert Martin","year":2008,"inStock":true}Имена полей становятся ключами JSON, а Java-типы сопоставляются с естественными JSON-типами: String — в строку в кавычках, int/double — в число, boolean — в true/false. Поле со значением null по умолчанию опускается.
Десериализация JSON в объекты
Обратное направление — десериализация — выполняется с помощью fromJson(). Вы передаёте JSON-текст и целевой Class, а Gson создаёт экземпляр и заполняет поля, сопоставляя ключи JSON с именами полей.
String json = "{\"title\":\"Clean Code\",\"author\":\"Robert Martin\",\"year\":2008,\"inStock\":true}";
Gson gson = new Gson();
Book b = gson.fromJson(json, Book.class);
System.out.println(b.title); // Clean Code
System.out.println(b.year); // 2008Если в JSON есть ключ, которому не соответствует ни одно поле, Gson его игнорирует; если у поля нет соответствующего ключа, оно остаётся со значением по умолчанию (null, 0 или false). Такое снисходительное поведение делает Gson устойчивым при добавлении API новых полей.
TypeToken для обобщённых коллекций
Из-за стирания типов Java удаляет информацию об обобщённых типах во время выполнения, поэтому gson.fromJson(json, List.class) не может знать, что вы хотите получить List<Book> — он вернёт List объектов LinkedTreeMap. Gson решает эту проблему с помощью TypeToken, который захватывает полный обобщённый тип, чтобы десериализация возвращала нужные элементы.
import com.google.gson.reflect.TypeToken;
import java.lang.reflect.Type;
import java.util.List;
String json = "[{\"title\":\"Effective Java\",\"year\":2018}]";
Type listType = new TypeToken<List<Book>>(){}.getType();
List<Book> books = gson.fromJson(json, listType);Используйте TypeToken всякий раз, когда целевой тип включает обобщения — List<T>, Map<K, V> или вложенные комбинации. Для простых необобщённых классов достаточно формы Book.class.
Красивый вывод и пользовательская конфигурация
По умолчанию Gson формирует компактный однострочный JSON. Для настройки поведения используйте GsonBuilder. Наиболее частый запрос — pretty printing — читаемый, с отступами вывод для логов и конфигурационных файлов.
import com.google.gson.GsonBuilder;
Gson gson = new GsonBuilder()
.setPrettyPrinting()
.serializeNulls() // include null fields instead of dropping them
.create();
System.out.println(gson.toJson(b));GsonBuilder управляет многими другими настройками. Вот те, что встречаются чаще всего:
| Метод builder | Эффект |
|---|---|
setPrettyPrinting() | Вывод с отступами в несколько строк |
serializeNulls() | Включать поля null вместо их пропуска |
setDateFormat(...) | Управлять форматированием значений Date |
registerTypeAdapter(...) | Подключить пользовательский сериализатор/десериализатор для типа |
excludeFieldsWithoutExposeAnnotation() | Сериализовать только поля, помеченные @Expose |
Для полного контроля над одним типом регистрируется пользовательский адаптер, реализующий JsonSerializer и/или JsonDeserializer — полезно, когда поле требует специального форматирования, которое рефлексия не может определить.
Практический пример: сериализация, разбор и цикл туда-обратно
Gson недоступен в окружении этого раннера, поэтому приведённая ниже программа демонстрирует те же концепции, используя только JDK: она вручную сериализует запись в JSON, разбирает текст обратно на поля, воссоздаёт объект и подтверждает, что цикл преобразования без потерь. Именно это автоматизируют для вас gson.toJson() и gson.fromJson().
Что можно извлечь из запуска:
- Первая строка показывает сериализацию: запись
Bookпревращается в{"title":"Clean Code","author":"Robert Martin","year":2008,"inStock":true}, где каждое поле — это ключ JSON, а типы сопоставлены с их естественными JSON-формами — именно это производитgson.toJson(). - Вторая строка показывает десериализацию: разбор этого текста и воссоздание
Book— ту же работу, чтоgson.fromJson(json, Book.class)выполняет за один вызов. Round-trip equal? trueподтверждает, что преобразование без потерь — сериализация с последующей десериализацией возвращает объект, равный оригиналу, что является свойством, к которому стремится каждый JSON-маппер.- Строка
Array JSONпоказываетList<Book>, отрисованный как массив объектов JSON — структуру, которую вы будете десериализовывать с помощьюTypeToken<List<Book>>. - Блок
Prettyпоказывает вывод с отступами в несколько строк, иллюстрируя то, что даётGsonBuilder.setPrettyPrinting()по сравнению с компактным вариантом по умолчанию.