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;
}
为方便起见,我们用Lombok 对它们进行了注解(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修饰符标记一个字段:
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 序列化程序将忽略声明为瞬态的每个字段:
String jsonString = new Gson().toJson(source);
assertEquals(expectedResult, jsonString);
虽然这非常快,但它也有一个严重的缺点:每个序列化工具都会考虑瞬态,不仅仅是 Gson。
Transient 是 Java 从序列化中排除的方式,那么我们的字段也将被Serializable的序列化以及管理我们对象的每个库工具或框架过滤掉。
此外,transient关键字始终适用于序列化和反序列化,这可能会根据用例进行限制。
4. @Expose注解
Gson *com.google.gson.annotations.@Expose *注解反其道而行之。
我们可以使用它来声明要序列化的字段,而忽略其他字段:
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. ExclusionStrategy
一个高度可定制的解决方案是使用com.google.gson.ExclusionStrategy 。 它允许我们定义(外部或使用匿名内部类)一种策略来指示 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;
}
};
这个 StackOverflow 答案 首先描述了这种技术。
它允许我们编写一次注解和策略,并且无需进一步修改就可以动态地注解我们的字段。
5.4. 将排除策略扩展到反序列化
无论我们将使用哪种策略,我们总是可以控制它应该应用在哪里。
仅在序列化期间:
Gson gson = new GsonBuilder().addSerializationExclusionStrategy(strategy)
仅在反序列化期间:
Gson gson = new GsonBuilder().addDeserializationExclusionStrategy(strategy)
总是:
Gson gson = new GsonBuilder().setExclusionStrategies(strategy);