1. Introduction
1.介绍
In this tutorial, we’ll explore a few advanced serialization and deserialization cases for List using Google’s Gson library.
在本教程中,我们将使用序列化和反序列化案例,为List探索一些高级Google的Gson库。
2. List of Objects
2.对象的清单
One common use case is to serialize and deserialize a list of POJOs.
一个常见的用例是序列化和反序列化一个POJO的列表。
Consider the class:
考虑一下这个班级。
public class MyClass {
private int id;
private String name;
public MyClass(int id, String name) {
this.id = id;
this.name = name;
}
// getters and setters
}
Here’s how we would serialize List<MyClass>:
下面是我们如何序列化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);
}
As we can see, serialization is fairly straightforward.
正如我们所看到的,序列化是相当直接的。
However, deserialization is tricky. Here’s an incorrect way of doing it:
然而,反序列化是很棘手的。这里有一个不正确的方法。
@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());
}
Here, although we would get a List of size two, post-deserialization, it wouldn’t be a List of MyClass. Therefore, line #6 throws ClassCastException.
在这里,虽然我们会得到一个大小为2的List,但在反序列化后,它不会是MyClass的List。因此,第6行抛出了ClassCastException。
Gson can serialize a collection of arbitrary objects but can’t deserialize the data without additional information. That’s because there’s no way for the user to indicate the type of the resulting object. Instead, while deserializing, the Collection must be of a specific, generic type.
Gson 可以序列化任意对象的集合,但在没有额外信息的情况下无法反序列化数据。这是因为用户没有办法指出所产生的对象的类型。相反,在反序列化时,Collection必须是一个特定的、通用的类型。
The correct way to deserialize the List would be:
对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);
}
Here, we use Gson’s TypeToken to determine the correct type to be deserialized – ArrayList<MyClass>. The idiom used to get the listOfMyClassObject actually defines an anonymous local inner class containing a method getType() that returns the fully parameterized type.
在这里,我们使用Gson的TypeToken来确定要被反序列化的正确类型–ArrayList<MyClass>。用于获取listOfMyClassObject的成语实际上定义了一个匿名的本地内类,其中包含一个返回完全参数化类型的方法getType()。
3. List of Polymorphic Objects
3.多态对象的列表
3.1. The Problem
3.1.问题
Consider an example class hierarchy of animals:
考虑一个动物的阶级层次的例子。
public abstract class Animal {
// ...
}
public class Dog extends Animal {
// ...
}
public class Cow extends Animal {
// ...
}
How do we serialize and deserialize List<Animal>? We could use TypeToken<ArrayList<Animal>> like we used in the previous section. However, Gson still won’t be able to figure out the concrete data type of the objects stored in the list.
我们如何序列化和反序列化List<Animal>?我们可以使用TypeToken<ArrayList<Animal>>,就像我们在上一节中使用的那样。然而,Gson仍然无法弄清存储在列表中的对象的具体数据类型。
3.2. Using Custom Deserializer
3.2.使用自定义反序列化器
One way to solve this is to add type information to the serialized JSON. We honor that type information during JSON deserialization. For this, we need to write our own custom serializer and deserializer.
解决这个问题的方法之一是在序列化的JSON中添加类型信息。在JSON反序列化过程中,我们要尊重该类型信息。为此,我们需要编写我们自己的自定义序列化器和反序列化器。
Firstly, we’ll introduce a new String field called type in the base class Animal. It stores the simple name of the class to which it belongs.
首先,我们将在基类Animal中引入一个新的String字段,称为type。它存储的是它所属类的简单名称。
Let’s take a look at our sample classes:
让我们看一下我们的样本班。
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
}
Serialization will continue to work as before without any issues:
序列化将继续像以前一样工作,没有任何问题。
@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);
}
In order to deserialize the list, we’ll have to provide a custom deserializer:
为了对列表进行反序列化,我们必须提供一个自定义的反序列化器。
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);
}
}
Here, the animalTypeRegistry map maintains the mapping between the class name and the class type.
在这里,animalTypeRegistry地图维护着类名和类类型之间的映射。
During deserialization, we first extract out the newly added type field. Using this value, we do a lookup on the animalTypeRegistry map to get the concrete data type. This data type is then passed to fromJson().
在反序列化过程中,我们首先提取出新添加的type字段。利用这个值,我们对animalTypeRegistry映射进行查找,以获得具体的数据类型。然后,这个数据类型被传递给fromJson()。
Let’s see how to use our custom deserializer:
让我们看看如何使用我们的自定义反序列化器。
@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. Using RuntimeTypeAdapterFactory
3.3.使用RuntimeTypeAdapterFactory
An alternative to writing a custom deserializer is to use the RuntimeTypeAdapterFactory class present in the Gson source code. However, it’s not exposed by the library for the user to use. Hence, we’ll have to create a copy of the class in our Java project.
替代编写自定义反序列化器的方法是使用RuntimeTypeAdapterFactory类,该类存在于Gson 源代码。然而,它并没有被库公开供用户使用。因此,我们必须在我们的Java项目中创建一个该类的副本。
Once this is done, we can use it to deserialize our list:
一旦这样做了,我们就可以用它来反序列化我们的列表。
@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);
}
Note that the underlying mechanism is still the same.
请注意,基本机制仍然是一样的。
We still need to introduce the type information during serialization. The type information can later be used during deserialization. Hence, the field type is still required in every class for this solution to work. We just don’t have to write our own deserializer.
我们仍然需要在序列化过程中引入类型信息。该类型信息随后可在反序列化过程中使用。因此,在这个解决方案中,每个类中仍然需要字段type。我们只是不必编写自己的反序列化器。
RuntimeTypeAdapterFactory provides the correct type adapter based on the field name passed to it and the registered subtypes.
RuntimeTypeAdapterFactory根据传递给它的字段名和注册的子类型,提供正确的类型适配器。
4. Conclusion
4.结论
In this article, we saw how to serialize and deserialize a list of objects using Gson.
在这篇文章中,我们看到了如何使用Gson对一个对象的列表进行序列化和反序列化。
As usual, the code is available over on GitHub.
像往常一样,代码可在GitHub上获得。