1. 概述
本文将系统梳理在使用 Gson 进行 JSON 序列化时,如何灵活地排除 Java 类及其子类中的一个或多个字段。这是日常开发中非常常见的需求,比如敏感字段脱敏、减少传输体积等。我们会从最简单的方式讲到高度可定制的策略,帮你避开一些常见“坑”。
2. 准备工作
先定义两个测试类,用 Lombok 简化代码(省去 getter/setter/构造函数等样板代码):
@Data
@AllArgsConstructor
public class MyClass {
private long id;
private String name;
private String other;
private MySubClass subclass;
}
@Data
@AllArgsConstructor
public class MySubClass {
private long id;
private String description;
private String otherVerboseInfo;
}
初始化数据:
MySubClass subclass = new MySubClass(42L, "the answer", "Verbose field not to serialize");
MyClass source = new MyClass(1L, "foo", "bar", subclass);
我们的目标是:序列化时跳过 MyClass.other
和 MySubClass.otherVerboseInfo
字段。
期望输出:
{
"id":1,
"name":"foo",
"subclass":{
"id":42,
"description":"the answer"
}
}
对应 Java 中的期望字符串:
String expectedResult = "{\"id\":1,\"name\":\"foo\",\"subclass\":{\"id\":42,\"description\":\"the answer\"}}";
接下来,我们看几种实现方式。
3. 使用 transient
关键字
最简单粗暴的方式:直接给字段加上 transient
修饰符。
public class MyClass {
private long id;
private String name;
private transient String other; // ❌ 不参与序列化
private MySubClass subclass;
}
public class MySubClass {
private long id;
private String description;
private transient String otherVerboseInfo; // ❌ 不参与序列化
}
Gson 会自动忽略所有 transient
字段:
String jsonString = new Gson().toJson(source);
assertEquals(expectedResult, jsonString); // ✅ 通过
✅ 优点:
- 零配置,开箱即用
- 性能最好
❌ 缺点:
- 影响全局:
transient
是 JVM 层面的序列化控制,所有序列化框架(如 Java 原生Serializable
、Jackson 等)都会遵循,失去灵活性 - 无法区分序列化/反序列化:
transient
对两者都生效,无法实现“只序列化时排除,反序列化时保留”的需求
⚠️ 所以,除非你明确希望所有序列化工具都忽略该字段,否则不推荐使用。
4. 使用 @Expose
注解
Gson 提供了 @Expose
注解,反向控制:只序列化被 @Expose
标记的字段。
先给需要保留的字段加上注解:
public class MyClass {
@Expose
private long id;
@Expose
private String name;
private String other; // 没有 @Expose,自动排除
@Expose
private MySubClass subclass;
}
public class MySubClass {
@Expose
private long id;
@Expose
private String description;
private String otherVerboseInfo; // 没有 @Expose,自动排除
}
⚠️ 注意:必须通过 GsonBuilder
创建 Gson 实例,并启用该机制:
Gson gson = new GsonBuilder()
.excludeFieldsWithoutExposeAnnotation() // 关键配置
.create();
String jsonString = gson.toJson(source);
assertEquals(expectedResult, jsonString); // ✅ 通过
✅ 优点:
- Gson 专属,不影响其他框架
- 可精细控制序列化/反序列化行为:
@Expose(serialize = false, deserialize = true)
private String other; // 只在反序列化时生效
❌ 缺点:
- 冗余严重:如果类有 100 个字段,只排除 1 个,那你得写 99 个
@Expose
,简直是灾难 - 侵入性强,代码变得啰嗦
⚠️ 适用于“白名单”场景(大多数字段要排除,少数保留),不适用于“黑名单”场景。
5. 使用 ExclusionStrategy
(推荐)
最灵活、最强大的方式是自定义 ExclusionStrategy
。你可以通过外部策略控制哪些字段或类需要被排除,完全解耦。
基本用法:
ExclusionStrategy strategy = new ExclusionStrategy() {
@Override
public boolean shouldSkipField(FieldAttributes field) {
// 返回 true 表示跳过该字段
return false;
}
@Override
public boolean shouldSkipClass(Class<?> clazz) {
// 返回 true 表示跳过整个类
return false;
}
};
Gson gson = new GsonBuilder()
.addSerializationExclusionStrategy(strategy) // 仅序列化时生效
.create();
5.1 按类名和字段名硬编码排除
最直接的方式,适合固定字段:
ExclusionStrategy strategy = new ExclusionStrategy() {
@Override
public boolean shouldSkipField(FieldAttributes field) {
if (field.getDeclaringClass() == MyClass.class && field.getName().equals("other")) {
return true;
}
if (field.getDeclaringClass() == MySubClass.class && field.getName().equals("otherVerboseInfo")) {
return true;
}
return false;
}
@Override
public boolean shouldSkipClass(Class<?> clazz) {
return false;
}
};
✅ 简单直接
❌ 难复用,字段重命名时容易出错
5.2 按业务规则动态排除
利用 shouldSkipField
返回 boolean 的特性,可以实现任意业务逻辑。例如:排除所有以 other
开头的字段:
ExclusionStrategy strategy = new ExclusionStrategy() {
@Override
public boolean shouldSkipClass(Class<?> clazz) {
return false;
}
@Override
public boolean shouldSkipField(FieldAttributes field) {
return field.getName().startsWith("other"); // ✅ 动态匹配
}
};
✅ 高度灵活,适合统一命名规范的场景
⚠️ 注意字段名拼写,避免误伤
5.3 自定义注解 + ExclusionStrategy(最佳实践)
结合自定义注解,实现类似 @Expose
但更符合直觉的“黑名单”模式。
先定义注解:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Exclude {}
在字段上使用:
public class MyClass {
private long id;
private String name;
@Exclude
private String other;
private MySubClass subclass;
}
public class MySubClass {
private long id;
private String description;
@Exclude
private String otherVerboseInfo;
}
策略实现:
ExclusionStrategy strategy = new ExclusionStrategy() {
@Override
public boolean shouldSkipClass(Class<?> clazz) {
return false;
}
@Override
public boolean shouldSkipField(FieldAttributes field) {
return field.getAnnotation(Exclude.class) != null; // 检查是否有 @Exclude
}
};
✅ 优点:
- 一次定义,到处使用
- 语义清晰,代码整洁
- 完全控制作用范围(可只用于序列化或反序列化)
这是目前最推荐的方式,兼顾灵活性与可维护性。
5.4 控制策略作用范围
ExclusionStrategy
可以精确控制应用时机:
仅序列化时排除:
Gson gson = new GsonBuilder() .addSerializationExclusionStrategy(strategy) .create();
仅反序列化时排除:
Gson gson = new GsonBuilder() .addDeserializationExclusionStrategy(strategy) .create();
两者都排除:
Gson gson = new GsonBuilder() .setExclusionStrategies(strategy) // 等效于同时设置 above two .create();
6. 总结
方式 | 适用场景 | 推荐度 |
---|---|---|
transient |
全局排除,所有序列化框架生效 | ⭐⭐ |
@Expose |
白名单模式,字段少 | ⭐⭐⭐ |
ExclusionStrategy + 自定义注解 |
黑名单模式,高可维护性 | ⭐⭐⭐⭐⭐ |
✅ 强烈推荐:使用 自定义注解 + ExclusionStrategy 的组合,既灵活又清晰,适合团队协作和长期维护。
⚠️ 避免滥用 transient
和 @Expose
,除非场景特别匹配。
完整示例代码已托管至 GitHub:https://github.com/your-repo/gson-exclude-demo