从 Gson 中的序列化中排除字段

1. 概述

在这个简短的教程中,我们将探索从 Gson 序列化中排除 Java 类及其子类的一个或多个字段的可用选项。

2. 初始设置

让我们首先定义我们的类:

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

为方便起见,我们用对它们进行了注释(getter、setter、构造函数的语法糖……)。

现在让我们填充它们:

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 ,不仅仅是 Gson。

Transient 是 Java 从序列化中排除的方式,那么我们的字段也将被Serializable的序列化以及管理我们对象的每个库工具或框架过滤掉。

此外,transient关键字始终适用于序列化和反序列化,这可能会根据用例进行限制。

4. @Expose注解

Gson com.google.gson.annotations 注释反其道而行之。

我们可以使用它来声明要序列化的字段,而忽略其他字段:

public class MyClass {
    @Expose 
    private long id;
    @Expose 
    private String name;
    private String other;
    @Expose 
    private MySubClass subclass;
}

public class MySubClass {
    @Expose 
    private long id;
    @Expose 
    private String description;
    private String otherVerboseInfo;
}

为此,我们需要使用 GsonBuilder 实例化 Gson:

Gson gson = new GsonBuilder()
  .excludeFieldsWithoutExposeAnnotation()
  .create();
String jsonString = gson.toJson(source);
assertEquals(expectedResult, jsonString);

这次我们可以在字段级别控制是否应该针对序列化、反序列化或两者(默认)进行过滤。

让我们看看如何防止MyClass.other被序列化,但允许在从 JSON 反序列化期间填充它:

@Expose(serialize = false, deserialize = true) 
private String other;

虽然这是 Gson 提供的最简单的方法,并且不会影响其他库,但它可能意味着代码中的冗余。如果我们有一个类有一百个字段,而我们只想排除一个字段,我们需要写九十九个注解,这有点大材小用。

5.排除策略

一个高度可定制的解决方案是使用。

它允许我们定义(外部或使用匿名内部类)一种策略来指示 GsonBuilder 是否使用自定义标准序列化字段(和/或类)。

Gson gson = new GsonBuilder()
  .addSerializationExclusionStrategy(strategy)
  .create();
String jsonString = gson.toJson(source);

assertEquals(expectedResult, jsonString);

让我们看一些使用智能策略的例子。

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. 使用业务标准

由于我们只需要返回一个布尔值,因此我们可以在该方法中实现我们喜欢的每个业务逻辑。

在以下示例中,我们将以“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. 使用自定义注释

另一种聪明的方法是创建一个自定义注释:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Exclude {}

然后我们可以利用ExclusionStrategy以使其与@Expose注释完全一样工作,但相反:

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

首先描述了这种技术。

它允许我们编写一次注解和策略,并且无需进一步修改就可以动态地注解我们的字段。

5.4. 将排除策略扩展到反序列化

无论我们将使用哪种策略,我们总是可以控制它应该应用在哪里。

仅在序列化期间:

Gson gson = new GsonBuilder().addSerializationExclusionStrategy(strategy)

仅在反序列化期间:

Gson gson = new GsonBuilder().addDeserializationExclusionStrategy(strategy)

总是:

Gson gson = new GsonBuilder().setExclusionStrategies(strategy);

6. 结论

我们已经看到了在 Gson 序列化期间从类及其子类中排除字段的不同方法。

我们还探讨了每种解决方案的主要优势和缺陷。

与往常一样,完整的源代码可获得。

经验分享 程序员 微信小程序 职场和发展