CodeGym /Cursos /JAVA 25 SELF /Gson — serialización y deserialización, configuración

Gson — serialización y deserialización, configuración

JAVA 25 SELF
Nivel 46 , Lección 2
Disponible

1. Introducción a Gson

Ya hemos conocido Jackson y hemos visto por qué se considera el estándar de facto para trabajar con JSON en Java. Pero hay otra biblioteca que ha ganado una enorme popularidad, especialmente en el mundo de Android — es Gson. Gson fue creada en Google como una solución ligera y sencilla para la serialización y deserialización de objetos de Java a JSON. Se valora por su bajísima barrera de entrada: para empezar a trabajar, casi no hay que configurar nada — la mayoría de tareas se resuelven literalmente «out of the box».

Otra ventaja de Gson es su ligereza. La biblioteca ocupa poco y no arrastra un montón de dependencias, por lo que se usa a menudo donde el tamaño de la aplicación es crítico, por ejemplo, en dispositivos móviles. Gson se ha convertido en el estándar de facto para los proyectos de Android — la compacidad y la sencillez juegan allí un papel decisivo.

Por cierto, el nombre Gson se descompone como Google JSON. A veces en la comunidad puede encontrarse una interpretación de broma — Genius’ Son («hijo del genio»), pero, por supuesto, no es oficial. Simplemente un juego de palabras.

Añadir Gson al proyecto

Si usas Maven o Gradle, simplemente añade la dependencia (la versión puede 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'

Aún no hemos estudiado la construcción de proyectos, así que para empezar puedes simplemente descargar el archivo jar desde la página oficial de Gson y añadirlo al proyecto.

2. Operaciones básicas: serialización y deserialización

Veamos cómo serializar y deserializar objetos con Gson usando una clase sencilla como ejemplo.

Ejemplo: clase User

// Clase para los ejemplos
public class User {
    private String name;
    private int age;
    private boolean active;

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

    // Getters y setters (Gson los usa cuando es necesario)
    public String getName() { return name; }
    public int getAge() { return age; }
    public boolean isActive() { return active; }
}

Serialización: 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}
    }
}

Ten en cuenta: los campos se serializan con sus nombres tal y como aparecen en la clase.

Deserialización: 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
    }
}

Trabajo con listas de objetos

Gson trabaja con colecciones un poco más complicado que Jackson, pero todo tiene solución.

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

        // Deserialización de la lista
        Type userListType = new TypeToken<List<User>>(){}.getType();
        List<User> users2 = gson.fromJson(json, userListType);
        System.out.println(users2.get(0).getName()); // Alice
    }
}

Matiz importante: ¡para deserializar colecciones utiliza TypeToken<>!

3. Configuración de Gson: GsonBuilder

Gson ofrece una configuración flexible a través de la clase GsonBuilder. Con ella puedes activar el pretty printing, la serialización de null, el formato de fechas y mucho más.

Ejemplo: pretty printing y serialización 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()        // Salida legible (indentación)
            .serializeNulls()           // Serializar campos null
            .create();

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

Formateo de fechas

Si tienes campos de tipo Date, por defecto Gson los serializa en un formato específico. Puedes establecer tu propio 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. Anotaciones de Gson: control de la serialización

Gson admite anotaciones para un control más preciso de la serialización y deserialización.

@SerializedName — cambio de nombre de un campo

Si quieres que un campo en el JSON tenga otro nombre, usa @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;
    }
}

Ahora, al serializar, el campo se llamará full_name:

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

@Expose — serializar solo los campos marcados

Si quieres serializar solo ciertos campos, utiliza @Expose y configura Gson:

import com.google.gson.annotations.Expose;

public class User {
    @Expose
    private String name;

    @Expose
    private int age;

    private boolean active; // no se serializa

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

Creamos Gson con soporte de @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 — serialización condicional por versión
Se pueden usar @Since y @Until para serializar campos solo para determinadas versiones (se usa poco en la práctica, pero conviene conocerlo).

5. Particularidades y limitaciones de Gson

Trabajo con objetos anidados

Gson funciona perfectamente con objetos anidados:

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

Trabajo con colecciones

Con la serialización de colecciones (List, Map) no hay problema, pero para la deserialización usa TypeToken (ver arriba).

Limitaciones de Gson frente a Jackson

  • Sin soporte para clases Java record (hasta versiones recientes)
  • Soporte limitado de las nuevas APIs de tipos de fecha y hora (por ejemplo, LocalDate, LocalDateTime — se necesitan adaptadores personalizados)
  • No funciona con anotaciones de Jackson
  • No admite estructuras polimórficas complejas «de serie»
  • No hay soporte automático para referencias bidireccionales (ciclos)

Adaptadores personalizados (TypeAdapter)

Si las capacidades estándar no son suficientes, puedes escribir tu propio adaptador para serializar/deserializar tipos complejos.

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áctica: serialización y deserialización con configuración

Vamos a ampliar tu aplicación de aprendizaje y añadir el guardado y la carga de una lista de usuarios en formato JSON.

Clase User con anotaciones

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; // no se serializa

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

    // getters, setters...
}

Guardamos la lista de usuarios en 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
          }
        ]
        */
    }
}

Cargamos la lista de usuarios desde 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. Comparación entre Gson y Jackson

Característica Gson Jackson
Facilidad de uso +++++ (muy sencillo) +++ (un poco más complejo)
Tamaño de la biblioteca Pequeño Más grande
Velocidad Rápido, pero un poco más lento Muy rápido
Flexibilidad Media Alta (más opciones de configuración)
Soporte de anotaciones Propias (@SerializedName) Propias (@JsonProperty y otras)
Soporte de tipos nuevos Limitado Excelente (Java 8+, record)
Soporte para Android Excelente Buena, pero más pesada
Trabajo con fechas Solo mediante adaptadores De serie
Polimorfismo Limitado Configurable de forma flexible

8. Errores típicos al trabajar con Gson

Error n.º 1: no usar TypeToken para colecciones.
Si deserializas una lista o un mapa, utiliza obligatoriamente TypeToken<>, de lo contrario tendrás errores extraños o colecciones vacías.

Error n.º 2: no hay constructor sin parámetros.
Gson puede funcionar sin constructor por defecto, pero a veces al deserializar objetos complejos sin dicho constructor pueden aparecer errores. Es mejor añadir siempre un constructor sin parámetros si planeas la deserialización.

Error n.º 3: desajuste de nombres de campos.
Si en JSON el campo se llama "full_name", y en la clase — "name", sin la anotación @SerializedName("full_name") el campo no se enlazará y el valor será null.

Error n.º 4: problemas con campos privados.
Gson puede serializar campos privados, pero si solo hay campos privados y no hay getters/setters, a veces surgen problemas durante la deserialización. Es mejor usar getters y setters.

Error n.º 5: trabajo con fechas.
Por defecto Gson serializa Date en un formato poco práctico. Para LocalDate, LocalDateTime y otros tipos nuevos, sin adaptadores personalizados habrá errores de serialización.

Error n.º 6: no usas @Expose pero has activado excludeFieldsWithoutExposeAnnotation().
Si has activado excludeFieldsWithoutExposeAnnotation(), pero no has marcado los campos con la anotación @Expose, no se serializarán ni se deserializarán — el resultado será un JSON vacío u objetos con null.

Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION