Gson 中的 @Expose 与 @SerializedName 注解

1、简介

Gson 是 Google 开发的开源 Java 库,用于简化对象与 JSON 之间的转换。它提供高效的序列化与反序列化技术,并支持复杂对象处理。

像 Gson 这样的库支持将 JSON 直接映射到 POJO。但某些场景下需要排除特定属性的序列化与反序列化。

本文将带你了解 Gson 库中两个关键注解:@Expose@SerializedName。尽管二者均涉及属性的序列化控制,但适用场景不同。

2、Gson 设置

要使用 Gson,需在 pom.xml 中添加其 Maven 依赖

<dependency>
    <groupId>com.google.code.gson</groupId>
    <artifactId>gson</artifactId>
    <version>2.10.1</version>
</dependency>

3、@Expose 注解

Gson 默认会序列化和反序列化 POJO 类的所有字段,除非另有指定。@Expose 注解可覆盖此行为,控制特定字段是否参与序列化或反序列化。

若字段的 serializedeserialize 属性设为 true,Gson 仅会处理带有 @Expose 注解的字段。这两个属性的默认值均为 true

以下示例展示了一个包含 idnameageemailUser 类。由于 email 是敏感信息,我们将其从输出的 JSON 中排除:

public class User {

    @Expose
    String name;

    @Expose
    int age;

    @Expose(serialize = true, deserialize = false)
    long id;
    
    @Expose(serialize = false, deserialize = false)
    private String email;

    // 构造函数、Getter、Setter 省略
}

在上述代码片段中,我们用 @Expose 标注了 nameage 字段。未显式设置 serializedeserialize 属性意味着它们默认为 true

需注意 email 属性的两个注解值均设为 false,因此序列化的 JSON 会忽略该字段。但需在创建 GsonBuilder 实例时调用 excludeFieldsWithoutExposeAnnotation() 方法才能使此配置生效。

另一方面,对于 id 字段,序列化过程会包含它,而反序列化时会忽略 JSON 中存在的任何 id 值:

@Test
public void givenUserObject_whenSerialized_thenCorrectJsonProduced() {
    User user = new User("John Doe", 30, "john.doe@example.com");
    user.setId(12345L);

    Gson gson = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create();
    String json = gson.toJson(user);

    // 验证 `name`、`age` 和 `id` 被序列化,而 `email` 未被包含
    assertEquals("{\"name\":\"John Doe\",\"age\":30,\"id\":12345}", json);
}
@Test
public void givenJsonInput_whenDeserialized_thenCorrectUserObjectProduced() {
    String jsonInput = "{\"name\":\"Jane Doe\",\"age\":25,\"id\":67890,\"email\":\"jane.doe@example.com\"}";

    Gson gson = new GsonBuilder().excludeFieldsWithoutExposeAnnotation()
      .create();
    User user = gson.fromJson(jsonInput, User.class);

    // 验证 `name` 和 `age` 被反序列化,而 `email` 和 `id` 未被处理

    assertEquals("Jane Doe", user.name);
    assertEquals(25, user.getAge());
    assertEquals(0, user.getId()); // id 未被反序列化
    assertNull(user.getEmail()); // email 未被反序列化
}

第一个测试中可见序列化的 JSON 未包含 email 属性。第二个单元测试验证了反序列化的 User 对象会忽略 JSON 中的 email 字段。

4、@SerializedName 注解

下面我们了解 Gson 中 @SerializedName 注解的用途。当 Java 类的属性名与其 JSON 表示需要不同时,该注解可将 POJO 属性映射到 JSON 中的指定字段名。

继续之前的示例,假设这次我们需要将 User 类的 name 字段在 JSON 中表示为 firstName

public class User {

    @Expose
    @SerializedName("firstName")
    String name;
}

测试,验证 firstName 字段:

@Test
public void givenUserObject_whenSerialized_thenCorrectJsonProduced() {
    User user = new User("John Doe", 30, "john.doe@example.com");
    user.setId(12345L);

    Gson gson = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create();
    String json = gson.toJson(user);

    assertEquals("{\"firstName\":\"John Doe\",\"age\":30,\"id\":12345}", json);
}

@SerializedName 支持一个名为 alternate 的附加属性,该属性接收字段的备选名称列表,指示解析器在反序列化时识别任意一个备选名称。当字段名称可能因外部系统或遗留系统差异而变化时,此功能非常实用。

假设某系统使用 fullName 而非 firstName,通过正确配置 alternate 属性即可准确解析:

public class User {

    @Expose
    @SerializedName(value = "firstName", alternate = { "fullName", "name" })
    String name;
}

测试:

@Test
public void givenJsonWithAlternateNames_whenDeserialized_thenCorrectNameFieldMapped() {
    String jsonInput1 = "{\"firstName\":\"Jane Doe\",\"age\":25,\"id\":67890,\"email\":\"jane.doe@example.com\"}";
    String jsonInput2 = "{\"fullName\":\"John Doe\",\"age\":30,\"id\":12345,\"email\":\"john.doe@example.com\"}";
    String jsonInput3 = "{\"name\":\"Alice\",\"age\":28,\"id\":54321,\"email\":\"alice@example.com\"}";

    Gson gson = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create();

    User user1 = gson.fromJson(jsonInput1, User.class);
    User user2 = gson.fromJson(jsonInput2, User.class);
    User user3 = gson.fromJson(jsonInput3, User.class);

    // 验证 `name` 字段可从不同的 JSON 字段名正确反序列化。

    assertEquals("Jane Doe", user1.getName());
    assertEquals("John Doe", user2.getName());
    assertEquals("Alice", user3.getName());
}

系统能够正确地从输入 JSON 数据中反序列化 name 属性,无论该属性在 JSON 中被命名为 fullName 还是 firstName

5、@SerializedName 和 @Expose 的对比

以下是两个注解的核心区别总结:

@SerializedName @Expose
将 Java POJO 字段映射到 JSON 字段名 标记字段是否应参与序列化或反序列化
必须提供 value 属性,可选择使用 alternate 属性 提供两个可选属性:serializedeserialize
开箱即用,无需任何配置 需通过 GsonBuilder 配置且必须调用 GsonBuilder.excludeFieldsWithoutExposeAnnotation() 才能生效

6、总结

本文介绍了如何使用 Gson 中的 @SerializedName@Expose 注解来处理 Java 中的 JSON 序列化与反序列化,同时强调了两者的核心差异。


Ref:https://www.baeldung.com/gson-expose-vs-serializedname-annotations