CodeGym /课程 /JAVA 25 SELF /Gson — 序列化与反序列化,配置

Gson — 序列化与反序列化,配置

JAVA 25 SELF
第 46 级 , 课程 2
可用

1. Gson 入门

我们已经接触过 Jackson,并了解了为什么它被视为 Java 处理中 JSON 的事实标准。但还有另一款在社区中、尤其在 Android 领域非常流行的库——GsonGson 由 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'

我们暂时还没有学习项目构建,因此起步阶段可以直接从 Gson 官方页面 下载 jar 文件并将其添加到项目中。

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

    // getter 和 setter(必要时 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 提供灵活的配置。你可以启用美化输出、序列化 null、设置日期格式等。

示例:美化输出与序列化 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;
    }
}

创建支持 @ExposeGson

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

处理集合

序列化集合(ListMap)没有问题,但反序列化时请使用 TypeToken(见上文)。

与 Jackson 相比的限制

  • (较早版本)不支持 Java record
  • 对新的日期时间 API 支持有限(例如 LocalDateLocalDateTime——需要自定义适配器)
  • 不能使用 Jackson 的注解
  • 不开箱即用地支持复杂的多态结构
  • 不支持双向引用(循环)的自动处理

自定义适配器(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;
    }

    // getter、setter...
}

将用户列表保存为 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:private 字段问题。
Gson 可以序列化 private 字段,但如果类中只有 private 字段且没有 getter/setter,反序列化时有时会出现问题。最好提供 getter 和 setter。

错误 5:日期处理。
默认情况下,GsonDate 序列化为不太方便的格式。对于 LocalDateLocalDateTime 等新类型,如果没有自定义适配器,会出现序列化问题。

错误 6:启用了 excludeFieldsWithoutExposeAnnotation() 却未使用 @Expose
如果你启用了 excludeFieldsWithoutExposeAnnotation(),但未给字段加上 @Expose 注解,这些字段将不会被序列化/反序列化——结果可能是空 JSON 或包含 null 的对象。

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