1. Wprowadzenie do Gson
Znamy już Jackson i widzieliśmy, dlaczego jest uznawany za de facto standard pracy z JSON w Javie. Ale jest też inna biblioteka, która zdobyła ogromną popularność, szczególnie w świecie Androida — to Gson. Gson powstał w Google jako lekkie i proste rozwiązanie do serializacji i deserializacji obiektów Javy do JSON. Ceni się go za niski próg wejścia: żeby zacząć, prawie nic nie trzeba konfigurować — większość zadań działa dosłownie „out of the box”.
Kolejną zaletą Gson jest jego lekkość. Biblioteka zajmuje mało miejsca i nie ściąga wielu zależności, dlatego często używa się jej tam, gdzie rozmiar aplikacji jest krytyczny, np. na urządzeniach mobilnych. Gson stał się faktycznym standardem dla projektów na Androidzie — kompaktowość i prostota odgrywają tam kluczową rolę.
Przy okazji, nazwa Gson rozwija się jako Google JSON. Czasami w społeczności można spotkać żartobliwą etymologię — Genius’ Son („syn geniusza”), ale to oczywiście nieoficjalne. Po prostu gra słów.
Dodanie Gson do projektu
Jeśli używasz Mavena lub Gradle, po prostu dodaj zależność (wersja może się różnić):
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'
Nie uczyliśmy jeszcze budowania projektów, więc na początek możesz po prostu pobrać plik jar z oficjalnej strony Gson i dodać go do projektu.
2. Podstawowe operacje: serializacja i deserializacja
Sprawdźmy, jak serializować i deserializować obiekty za pomocą Gson na przykładzie prostej klasy.
Przykład: klasa User
// Klasa do przykładów
public class User {
private String name;
private int age;
private boolean active;
// Konstruktor
public User(String name, int age, boolean active) {
this.name = name;
this.age = age;
this.active = active;
}
// Gettery i settery (Gson używa ich w razie potrzeby)
public String getName() { return name; }
public int getAge() { return age; }
public boolean isActive() { return active; }
}
Serializacja: obiekt → 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}
}
}
Uwaga: pola są serializowane z ich nazwami zdefiniowanymi w klasie!
Deserializacja: JSON → obiekt
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
}
}
Praca z listami obiektów
Gson pracuje z kolekcjami nieco trudniej niż Jackson, ale wszystko jest do ogarnięcia.
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}]
// Deserializacja listy
Type userListType = new TypeToken<List<User>>(){}.getType();
List<User> users2 = gson.fromJson(json, userListType);
System.out.println(users2.get(0).getName()); // Alice
}
}
Ważna uwaga: dla deserializacji kolekcji używaj TypeToken<>!
3. Konfiguracja Gson: GsonBuilder
Gson udostępnia elastyczną konfigurację przez klasę GsonBuilder. Z jej pomocą możesz włączyć pretty printing, serializację null, formatowanie dat i wiele więcej.
Przykład: pretty printing i serializacja 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() // Ładny wydruk (wcięcia)
.serializeNulls() // Serializuj pola null
.create();
String json = gson.toJson(user);
System.out.println(json);
/*
{
"name": "Charlie",
"age": 0,
"active": false
}
*/
}
}
Formatowanie dat
Jeśli masz pola typu Date, domyślnie Gson serializuje je w specyficznym formacie. Można ustawić własny format:
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. Adnotacje Gson: kontrola serializacji
Gson wspiera adnotacje dla bardziej precyzyjnej kontroli nad serializacją i deserializacją.
@SerializedName — zmiana nazwy pola
Jeśli chcesz, aby pole w JSON miało inną nazwę, użyj @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;
}
}
Teraz podczas serializacji pole będzie się nazywać full_name:
User user = new User("Diana", 28, true);
String json = new Gson().toJson(user);
// {"full_name":"Diana","age":28,"active":true}
@Expose — serializacja tylko oznaczonych pól
Jeśli chcesz serializować tylko wybrane pola, użyj @Expose i skonfiguruj Gson:
import com.google.gson.annotations.Expose;
public class User {
@Expose
private String name;
@Expose
private int age;
private boolean active; // nie jest serializowane
public User(String name, int age, boolean active) {
this.name = name;
this.age = age;
this.active = active;
}
}
Tworzymy Gson z obsługą @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 — warunkowa serializacja według wersji
Można używać @Since i @Until do serializowania pól tylko dla określonych wersji (rzadko używane w praktyce, ale warto wiedzieć).
5. Cechy i ograniczenia Gson
Praca z obiektami zagnieżdżonymi
Gson świetnie radzi sobie z obiektami zagnieżdżonymi:
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"}
Praca z kolekcjami
Z serializacją kolekcji (List, Map) nie ma problemu, ale do deserializacji używaj TypeToken (patrz wyżej).
Ograniczenia Gson w porównaniu z Jacksonem
- Brak wsparcia klas record w Javie (do najnowszych wersji)
- Ograniczone wsparcie nowego API typów daty/czasu (np. LocalDate, LocalDateTime — wymagane niestandardowe adaptery)
- Nie obsługuje adnotacji Jackson
- Brak wsparcia złożonych struktur polimorficznych „out of the box”
- Brak automatycznej obsługi dwukierunkowych odwołań (cykliczności)
Niestandardowe adaptery (TypeAdapter)
Jeśli standardowe możliwości to za mało, możesz napisać własny adapter do serializacji/deserializacji złożonych typów.
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;
}
}
// Użycie:
Gson gson = new GsonBuilder()
.registerTypeAdapter(Boolean.class, new BooleanAsIntAdapter())
.create();
6. Praktyka: serializacja i deserializacja z konfiguracją
Rozwińmy twoją aplikację edukacyjną i dodajmy zapisywanie i wczytywanie listy użytkowników w formacie JSON.
Klasa User z adnotacjami
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; // nie jest serializowane
public User(String name, int age, boolean active) {
this.name = name;
this.age = age;
this.active = active;
}
// gettery, settery...
}
Zapisujemy listę użytkowników do 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
}
]
*/
}
}
Wczytujemy listę użytkowników z 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. Porównanie Gson i Jackson
| Cecha | Gson | Jackson |
|---|---|---|
| Łatwość użycia | +++++ (bardzo proste) | +++ (nieco trudniejsze) |
| Rozmiar biblioteki | Mały | Większy |
| Szybkość | Szybko, ale odrobinę wolniej | Bardzo szybko |
| Elastyczność | Średnia | Wysoka (więcej ustawień) |
| Obsługa adnotacji | Własne (@SerializedName) | Własne (@JsonProperty i inne) |
| Obsługa nowych typów | Ograniczona | Świetna (Java 8+, record) |
| Wsparcie dla Androida | Świetna | Dobra, ale cięższa |
| Praca z datami | Tylko przez adaptery | Wbudowana |
| Polimorfizm | Ograniczony | Elastycznie konfigurowalny |
8. Typowe błędy podczas pracy z Gson
Błąd nr 1: Nie używasz TypeToken do kolekcji.
Jeśli deserializujesz listę lub mapę, koniecznie używaj TypeToken<>, w przeciwnym razie pojawią się dziwne błędy albo puste kolekcje.
Błąd nr 2: Brak konstruktora bezparametrowego.
Gson potrafi działać także bez konstruktora domyślnego, ale czasem przy deserializacji złożonych obiektów bez takiego konstruktora mogą wystąpić błędy. Lepiej zawsze dodać konstruktor bez parametrów, jeśli planujesz deserializację.
Błąd nr 3: Niezgodność nazw pól.
Jeśli w JSON pole nazywa się "full_name", a w klasie — "name", bez adnotacji @SerializedName("full_name") pole nie zostanie powiązane i wartość będzie null.
Błąd nr 4: Problemy z polami prywatnymi.
Gson potrafi serializować prywatne pola, ale jeśli są tylko prywatne pola i brak getterów/setterów, czasem pojawiają się problemy przy deserializacji. Lepiej używać getterów i setterów.
Błąd nr 5: Praca z datami.
Domyślnie Gson serializuje Date w mało wygodnym formacie. Dla LocalDate, LocalDateTime i innych nowych typów bez niestandardowych adapterów wystąpią błędy serializacji.
Błąd nr 6: Nie używasz @Expose, ale włączyłeś excludeFieldsWithoutExposeAnnotation().
Jeśli włączyłeś excludeFieldsWithoutExposeAnnotation(), ale nie oznaczyłeś pól adnotacją @Expose, nie będą one serializowane ani deserializowane — rezultat to pusty JSON albo obiekty z null.
GO TO FULL VERSION