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序列化器會忽略所有聲明為 transient 的字段。
String jsonString = new Gson().toJson(source);
assertEquals(expectedResult, jsonString);
雖然這種方法非常快速,但也伴隨着嚴重的缺點:每一個序列化工具都會考慮 transient,不僅僅是 Gson。
Transient 是 Java 中用來排除從序列化而來的字段的方式,因此我們的字段也會被 Serializable 的序列化以及所有管理我們對象的庫工具或框架過濾。
此外,transient 關鍵字在序列化和反序列化過程中始終有效,這在某些用例中可能會有所限制。
4. @Expose 註解
Gson 的 @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. 排除策略
使用一個 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);
6. 結論
我們已經看到了在 Gson 序列化過程中從類及其子類中排除字段的不同方法。
我們還探討了每種解決方案的主要優勢和潛在問題。