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.otherMySubClass.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


原始标题:Exclude Fields from Serialization in Gson