CodeGym /Cursos /JAVA 25 SELF /Gson — serialização e desserialização, configuração

Gson — serialização e desserialização, configuração

JAVA 25 SELF
Nível 46 , Lição 2
Disponível

1. Introdução ao Gson

Já conhecemos o Jackson e vimos por que ele é considerado o padrão de fato para trabalhar com JSON em Java. Mas há outra biblioteca que ganhou enorme popularidade, especialmente no mundo Android — o Gson. O Gson foi criado no Google como uma solução leve e simples para serializar e desserializar objetos Java em JSON. Ele é valorizado pelo baixíssimo custo de entrada: para começar, quase nada precisa ser configurado — a maioria das tarefas funciona literalmente “pronta para uso”.

Outro ponto forte do Gson é sua leveza. A biblioteca ocupa pouco espaço e não puxa um monte de dependências, por isso é muito usada onde o tamanho do aplicativo é crítico, por exemplo, em dispositivos móveis. O Gson se tornou o padrão de fato para projetos Android — compacidade e simplicidade são decisivas nesse contexto.

Aliás, o nome Gson significa Google JSON. Às vezes, na comunidade, aparece uma interpretação bem-humorada — “Genius’ Son” (“filho do gênio”), mas isso, claro, não é oficial. Apenas um jogo de palavras.

Adicionando o Gson ao projeto

Se você usa Maven ou Gradle, basta adicionar a dependência (a versão pode variar):

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'

Ainda não estudamos a construção de projetos, então, para começar, você pode simplesmente baixar o arquivo jar da página oficial do Gson e adicioná-lo ao projeto.

2. Operações básicas: serialização e desserialização

Vamos ver como serializar e desserializar objetos com Gson usando um exemplo de classe simples.

Exemplo: classe User

// Classe de exemplo
public class User {
    private String name;
    private int age;
    private boolean active;

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

    // Getters e setters (o Gson os usa quando necessário)
    public String getName() { return name; }
    public int getAge() { return age; }
    public boolean isActive() { return active; }
}

Serialização: objeto → 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}
    }
}

Atenção: os campos são serializados com seus nomes definidos na classe!

Desserialização: JSON → objeto

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
    }
}

Trabalhando com listas de objetos

Gson lida com coleções de forma um pouco mais trabalhosa do que o Jackson, mas é totalmente viável.

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}]

        // Desserialização de lista
        Type userListType = new TypeToken<List<User>>(){}.getType();
        List<User> users2 = gson.fromJson(json, userListType);
        System.out.println(users2.get(0).getName()); // Alice
    }
}

Observação importante: para desserializar coleções, use TypeToken<>!

3. Configuração do Gson: GsonBuilder

O Gson oferece configuração flexível por meio da classe GsonBuilder. Com ela, você pode ativar pretty printing, serialização de null, formatação de datas e muito mais.

Exemplo: pretty printing e serialização de 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()        // Saída formatada (indentação)
            .serializeNulls()           // Serializar campos null
            .create();

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

Formatação de datas

Se você tem campos do tipo Date, por padrão o Gson os serializa em um formato específico. É possível definir seu próprio formato:

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. Anotações do Gson: controle da serialização

Gson oferece anotações para controle mais preciso da serialização e desserialização.

@SerializedName — renomeando campo

Se você quiser que um campo no JSON tenha outro nome, use @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;
    }
}

Agora, na serialização, o campo se chamará full_name:

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

@Expose — serializar apenas campos anotados

Se você deseja serializar apenas determinados campos, use @Expose e configure o Gson:

import com.google.gson.annotations.Expose;

public class User {
    @Expose
    private String name;

    @Expose
    private int age;

    private boolean active; // não é serializado

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

Criando um Gson com suporte a @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 — serialização condicional por versão
Você pode usar @Since e @Until para serializar campos apenas para determinadas versões (raramente usado na prática, mas é útil conhecer).

5. Características e limitações do Gson

Trabalhando com objetos aninhados

Gson funciona muito bem com objetos aninhados:

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"}

Trabalhando com coleções

Serializar coleções (List, Map) não é problema, mas para desserializar use TypeToken (veja acima).

Limitações do Gson em comparação com o Jackson

  • Sem suporte a classes record do Java (até versões recentes)
  • Suporte limitado às APIs novas de data/hora (por exemplo, LocalDate, LocalDateTime — exigem adaptadores personalizados)
  • Não funciona com as anotações do Jackson
  • Sem suporte para estruturas polimórficas complexas “prontas para uso”
  • Sem suporte automático a referências bidirecionais (ciclicidade)

Adaptadores personalizados (TypeAdapter)

Se os recursos padrão não forem suficientes, você pode escrever seu próprio adaptador para serializar/desserializar tipos complexos.

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;
    }
}

// Uso:
Gson gson = new GsonBuilder()
    .registerTypeAdapter(Boolean.class, new BooleanAsIntAdapter())
    .create();

6. Prática: serialização e desserialização com configurações

Vamos evoluir seu aplicativo de estudos e adicionar o salvamento e o carregamento de uma lista de usuários em formato JSON.

Classe User com anotações

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; // não é serializado

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

    // getters, setters...
}

Salvando a lista de usuários em 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
          }
        ]
        */
    }
}

Carregando a lista de usuários a partir de 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. Comparação: Gson e Jackson

Característica Gson Jackson
Facilidade de uso +++++ (muito simples) +++ (um pouco mais complexo)
Tamanho da biblioteca Pequeno Maior
Velocidade Rápido, mas um pouco mais lento Muito rápido
Flexibilidade Média Alta (mais configurações)
Suporte a anotações Próprias (@SerializedName) Próprias (@JsonProperty etc.)
Suporte a novos tipos Limitado Excelente (Java 8+, record)
Suporte a Android Excelente Bom, porém mais pesado
Trabalhar com datas Somente via adaptadores Pronto para uso
Polimorfismo Limitado Altamente configurável

8. Erros comuns ao trabalhar com Gson

Erro nº 1: Você não usa TypeToken para coleções.
Se for desserializar uma lista ou um mapa, use obrigatoriamente TypeToken<>, caso contrário você terá erros estranhos ou coleções vazias.

Erro nº 2: Não há construtor sem parâmetros.
O Gson pode funcionar sem construtor padrão, mas às vezes, ao desserializar objetos complexos sem esse construtor, podem ocorrer erros. É melhor sempre adicionar um construtor sem parâmetros se você planeja desserializar.

Erro nº 3: Nomes de campos não correspondem.
Se no JSON o campo se chama "full_name", e na classe — "name", sem a anotação @SerializedName("full_name") o campo não será associado e o valor será null.

Erro nº 4: Problemas com campos privados.
O Gson pode serializar campos privados, mas se houver apenas campos privados e não houver getters/setters, às vezes surgem problemas na desserialização. É melhor usar getters e setters.

Erro nº 5: Trabalho com datas.
Por padrão, o Gson serializa Date em um formato pouco prático. Para LocalDate, LocalDateTime e outros tipos novos, sem adaptadores personalizados ocorrerão erros de serialização.

Erro nº 6: Você não usa @Expose, mas ativou excludeFieldsWithoutExposeAnnotation().
Se você ativou excludeFieldsWithoutExposeAnnotation(), mas não anotou os campos com @Expose, eles não serão serializados nem desserializados — o resultado será um JSON vazio ou objetos com null.

Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION