Exclude Fields from Serialization in Gson – 在Gson中从序列化中排除字段

最后修改: 2018年 6月 1日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

In this short tutorial, we’re going to explore the available options for excluding one or more fields of a Java class and its subclasses from Gson serialization.

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

2. Initial Setup

2.初始设置

Let’s first define our classes:

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

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

We’ve annotated them with Lombok for convenience (syntactic sugar for getters, setters, constructors…).

为了方便起见,我们用Lombok对它们进行了注释(getters、setters、constructors…的语法糖)。

Let’s now populate them:

现在让我们来填充它们。

MySubClass subclass = new MySubClass(42L, "the answer", "Verbose field not to serialize")
MyClass source = new MyClass(1L, "foo", "bar", subclass);

Our goal is to prevent the MyClass.other and MySubClass.otherVerboseInfo fields from being serialized.

我们的目标是防止MyClass.otherMySubClass.otherVerboseInfo域被序列化。

The output we are expecting to get is:

我们期望得到的输出是。

{
  "id":1,
  "name":"foo",
  "subclass":{
    "id":42,
    "description":"the answer"
  }
}

In Java:

在Java中。

String expectedResult = "{\"id\":1,\"name\":\"foo\",\"subclass\":{\"id\":42,\"description\":\"the answer\"}}";

3. Transient Modifier

3.瞬态修改器

We can mark a field with the transient modifier:

我们可以用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 serializer will ignore every field declared as transient:

Gson序列化器将忽略每一个声明为瞬时的字段。

String jsonString = new Gson().toJson(source);
assertEquals(expectedResult, jsonString);

While this is very fast, it also comes with a severe downside: every serialization tool will take transient into account, not only Gson.

虽然这非常快,但它也有一个严重的缺点。每一个序列化工具都会考虑到瞬时性,不仅仅是Gson。

Transient is the Java way to exclude from serialization, then our field will also be filtered out by Serializable‘s serialization, and by every library tool or framework managing our objects.

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

Additionally, the transient keyword always works for both serialization and deserialization, which can be limiting depending on the use-cases.

此外,transient关键字总是同时适用于序列化和反序列化,这可能会根据使用情况而产生限制。

4. @Expose Annotation

4.@Expose 注释

Gson com.google.gson.annotations @Expose annotation works the other way around.

Gson com.google.gson.annotations @Expose 注释的工作方式与之相反。

We can use it to declare which fields to serialize, and ignore the others:

我们可以用它来声明哪些字段要被序列化,而忽略其他字段。

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

For this, we need to instantiate Gson with a GsonBuilder:

为此,我们需要用一个GsonBuilder来实例化Gson。

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

This time we can control at field level whether the filtering should happen for serialization, deserialization, or both (default).

这一次,我们可以在字段级控制过滤是否应该发生在序列化、反序列化或两者(默认)。

Let’s see how to prevent MyClass.other from being serialized, but allowing it to be populated during a deserialization from JSON:

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

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

While this is the easiest way provided by Gson, and it doesn’t affect other libraries, it could imply redundancy in the code. If we have a class with a hundred of fields, and we only want to exclude one field, we need to write ninety-nine annotation, which is overkill.

虽然这是Gson提供的最简单的方法,而且不影响其他库,但它可能意味着代码中的冗余。如果我们有一个有上百个字段的类,而我们只想排除一个字段,我们需要写99个注解,这就显得多余了。

5. ExclusionStrategy

5.排除策略

A highly customizable solution is the usage of a com.google.gson.ExclusionStrategy.

一个高度可定制的解决方案是使用com.google.gson.ExclusionStrategy

It allows us to define (externally or with an Anonimous Inner Class) a strategy to instruct the GsonBuilder whether to serialize fields (and/or classes) with custom criteria.

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

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

assertEquals(expectedResult, jsonString);

Let’s see some examples of smart strategies to use.

让我们看看一些可以使用的聪明策略的例子。

5.1. With Classes and Fields Names

5.1.有了类和字段名

Of course, we can also hardcode one or more fields/classes names:

当然,我们也可以硬编码一个或多个字段/类的名称。

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

This is fast and straight to the point, but not very reusable and also prone to errors in case we rename our attributes.

这很快速,而且直奔主题,但可重用性不高,而且万一我们重命名我们的属性,也很容易出错。

5.2. With Business Criteria

5.2.有业务标准

Since we simply have to return a boolean, we can implement every business logic we like inside that method.

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

In the following example, we’ll identify every field starting with “other” as fields which shouldn’t be serialized, no matter the class they belong:

在下面的例子中,我们将把每一个以 “其他 “开头的字段确定为不应该被序列化的字段,不管它们属于哪个类。

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. With a Custom Annotation

5.3.使用自定义注释

Another smart approach is to create a custom annotation:

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

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

We can then exploit ExclusionStrategy in order to make it work exactly as with the @Expose annotation, but inversely:

然后我们可以利用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;
}

And here is the strategy:

而这里是战略。

ExclusionStrategy strategy = new ExclusionStrategy() {
    @Override
    public boolean shouldSkipClass(Class<?> clazz) {
        return false;
    }

    @Override
    public boolean shouldSkipField(FieldAttributes field) {
        return field.getAnnotation(Exclude.class) != null;
    }
};

This StackOverflow answer firstly described this technique.

这个StackOverflow答案首先描述了这种技术。

It allows us to write the annotation and the Strategy once, and to dynamically annotate our fields without further modification.

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

5.4. Extend Exclusion Strategy to Deserialization

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

No matter which strategy we’ll use, we can always control where it should be applied.

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

Only during serialization:

仅在序列化过程中。

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

Only during deserialization:

只在反序列化过程中。

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

Always:

总是这样。

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

6. Conclusion

6.结论

We’ve seen different ways to exclude fields from a class and its subclasses during Gson serialization.

我们已经看到了在Gson序列化过程中排除一个类和它的子类的字段的不同方法。

We’ve also explored the main advantages and pitfalls of every solution.

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

As always, the full source code is available over on Github.

像往常一样,完整的源代码可在Github上获得