1. 引言
本教程將探討使用 List 以及 Google Gson 庫 的一些高級序列化和反序列化案例。
2. 對象列表
一個常見用例是將 POJO 列表序列化和反序列化。
請考慮以下類:
public class MyClass {
private int id;
private String name;
public MyClass(int id, String name) {
this.id = id;
this.name = name;
}
// getters and setters
}以下是如何序列化 List<MyClass>:
@Test
public void givenListOfMyClass_whenSerializing_thenCorrect() {
List<MyClass> list = Arrays.asList(new MyClass(1, "name1"), new MyClass(2, "name2"));
Gson gson = new Gson();
String jsonString = gson.toJson(list);
String expectedString = "[{\"id\":1,\"name\":\"name1\"},{\"id\":2,\"name\":\"name2\"}]";
assertEquals(expectedString, jsonString);
}如我們所見,序列化過程相對簡單。
然而,反序列化卻很複雜。以下是一種錯誤的實現方式:
@Test(expected = ClassCastException.class)
public void givenJsonString_whenIncorrectDeserializing_thenThrowClassCastException() {
String inputString = "[{\"id\":1,\"name\":\"name1\"},{\"id\":2,\"name\":\"name2\"}]";
Gson gson = new Gson();
List<MyClass> outputList = gson.fromJson(inputString, ArrayList.class);
assertEquals(1, outputList.get(0).getId());
}在這裏,儘管經過反序列化後我們會得到一個大小為二的 List,但它不會是 MyClass 的 List。因此,第6行拋出了 ClassCastException。
Gson 可以序列化任意對象的集合,但無法在缺少額外信息的情況下進行反序列化。這是因為用户無法指示生成對象的類型。 而是,在反序列化過程中,Collection 必須具有特定的泛型類型。
正確反序列化 List 的方法如下:
@Test
public void givenJsonString_whenDeserializing_thenReturnListOfMyClass() {
String inputString = "[{\"id\":1,\"name\":\"name1\"},{\"id\":2,\"name\":\"name2\"}]";
List<MyClass> inputList = Arrays.asList(new MyClass(1, "name1"), new MyClass(2, "name2"));
Type listOfMyClassObject = new TypeToken<ArrayList<MyClass>>() {}.getType();
Gson gson = new Gson();
List<MyClass> outputList = gson.fromJson(inputString, listOfMyClassObject);
assertEquals(inputList, outputList);
}在這裏,我們使用 Gson 的 TypeToken 來確定需要反序列化的正確類型——ArrayList<MyClass>。 使用 listOfMyClassObject 這個表達方式實際上定義了一個匿名局部內部類,其中包含一個方法 getType(),該方法返回完全參數化的類型。
3. 多態對象列表
3.1. 問題
考慮一個動物類別的示例繼承層次結構:
public abstract class Animal {
// ...
}
public class Dog extends Animal {
// ...
}
public class Cow extends Animal {
// ...
}我們如何序列化和反序列化 List<Animal>?我們可以使用 TypeToken<ArrayList<Animal>>,就像我們在上一節中使用的那樣。但是 Gson 仍然無法確定列表中存儲的對象的具體數據類型。
3.2. 使用自定義反序列器
一種解決此問題的辦法是將類型信息添加到序列化的 JSON 中。我們會在 JSON 反序列化過程中尊重這些類型信息。為此,我們需要編寫自定義的序列化器和反序列器。
首先,我們將向基類 Animal 添加一個名為 type 的新 String 字段,用於存儲它所屬的簡單類名。
讓我們來看一下我們的示例類:
public abstract class Animal {
public String type = "Animal";
}public class Dog extends Animal {
private String petName;
public Dog() {
petName = "Milo";
type = "Dog";
}
// getters and setters
}public class Cow extends Animal {
private String breed;
public Cow() {
breed = "Jersey";
type = "Cow";
}
// getters and setters
}序列化將繼續像以前一樣正常工作,沒有任何問題:
@Test
public void givenPolymorphicList_whenSerializeWithTypeAdapter_thenCorrect() {
String expectedString
= "[{\"petName\":\"Milo\",\"type\":\"Dog\"},{\"breed\":\"Jersey\",\"type\":\"Cow\"}]";
List<Animal> inList = new ArrayList<>();
inList.add(new Dog());
inList.add(new Cow());
String jsonString = new Gson().toJson(inList);
assertEquals(expectedString, jsonString);
}為了反序列化列表,我們需要提供一個自定義的反序列化器:
public class AnimalDeserializer implements JsonDeserializer<Animal> {
private String animalTypeElementName;
private Gson gson;
private Map<String, Class<? extends Animal>> animalTypeRegistry;
public AnimalDeserializer(String animalTypeElementName) {
this.animalTypeElementName = animalTypeElementName;
this.gson = new Gson();
this.animalTypeRegistry = new HashMap<>();
}
public void registerBarnType(String animalTypeName, Class<? extends Animal> animalType) {
animalTypeRegistry.put(animalTypeName, animalType);
}
public Animal deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) {
JsonObject animalObject = json.getAsJsonObject();
JsonElement animalTypeElement = animalObject.get(animalTypeElementName);
Class<? extends Animal> animalType = animalTypeRegistry.get(animalTypeElement.getAsString());
return gson.fromJson(animalObject, animalType);
}
}在這裏,animalTypeRegistry 映射維護了類名與其類類型的映射關係。
在反序列化過程中,我們首先提取新增的 type 字段。利用該值,我們在 animalTypeRegistry 映射中進行查找,以獲取具體的類類型。然後將該類類型傳遞給 fromJson()。
下面是如何使用我們的自定義反序列器:
@Test
public void givenPolymorphicList_whenDeserializeWithTypeAdapter_thenCorrect() {
String inputString
= "[{\"petName\":\"Milo\",\"type\":\"Dog\"},{\"breed\":\"Jersey\",\"type\":\"Cow\"}]";
AnimalDeserializer deserializer = new AnimalDeserializer("type");
deserializer.registerBarnType("Dog", Dog.class);
deserializer.registerBarnType("Cow", Cow.class);
Gson gson = new GsonBuilder()
.registerTypeAdapter(Animal.class, deserializer)
.create();
List<Animal> outList = gson.fromJson(inputString, new TypeToken<List<Animal>>(){}.getType());
assertEquals(2, outList.size());
assertTrue(outList.get(0) instanceof Dog);
assertTrue(outList.get(1) instanceof Cow);
}3.3. 使用 RuntimeTypeAdapterFactory 類
另一種方法是使用 Gson 庫中提供的 RuntimeTypeAdapterFactory 類。但是,該類未直接暴露給用户使用。因此,我們需要在我們的 Java 項目中創建一個該類的副本。
完成此操作後,我們可以使用它來反序列化我們的列表:
@Test
public void givenPolymorphicList_whenDeserializeWithRuntimeTypeAdapter_thenCorrect() {
String inputString
= "[{\"petName\":\"Milo\",\"type\":\"Dog\"},{\"breed\":\"Jersey\",\"type\":\"Cow\"}]";
Type listOfAnimals = new TypeToken<ArrayList<Animal>>(){}.getType();
RuntimeTypeAdapterFactory<Animal> adapter = RuntimeTypeAdapterFactory.of(Animal.class, "type")
.registerSubtype(Dog.class)
.registerSubtype(Cow.class);
Gson gson = new GsonBuilder().registerTypeAdapterFactory(adapter).create();
List<Animal> outList = gson.fromJson(inputString, listOfAnimals);
assertEquals(2, outList.size());
assertTrue(outList.get(0) instanceof Dog);
assertTrue(outList.get(1) instanceof Cow);
}請注意,底層機制仍然相同。
在序列化過程中,仍需要引入類型信息。該類型信息可用於後續的反序列化。 因此,為了使該解決方案有效,每個類中仍需包含 type 字段。 我們無需編寫自己的反序列器。
RuntimeTypeAdapterFactory 根據傳遞給它字段名稱和已註冊的子類型,提供正確的類型適配器。
4. 結論
在本文中,我們學習瞭如何使用 Gson 序列化和反序列化對象列表。