Перейти к основному содержимому

Сериализация и десериализация списка с помощью Gson

· 5 мин. чтения

1. Введение

В этом руководстве мы рассмотрим несколько расширенных случаев сериализации и десериализации для списка с использованием библиотеки Gson от Google .

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

Здесь, несмотря на то, что мы получили бы список второго размера, после десериализации это не был бы список MyClass . Поэтому строка № 6 выдает ClassCastException .

Gson может сериализовать набор произвольных объектов, но не может десериализовать данные без дополнительной информации. Это связано с тем, что пользователь не может указать тип результирующего объекта. Вместо этого при десериализации коллекция должна относиться к определенному общему типу.

Правильный способ десериализации списка :

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

Здесь мы используем TypeToken Gson , чтобы определить правильный тип для десериализации — 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. Для этого нам нужно написать собственный сериализатор и десериализатор.

Во- первых, мы введем новое поле String с именем type в базовом классе Animal . В нем хранится простое имя класса, к которому он принадлежит.

Давайте взглянем на наши примеры классов:

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 поддерживает сопоставление между именем класса и типом класса.

Во время десериализации мы сначала извлекаем только что добавленное поле типа . Используя это значение, мы просматриваем карту 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

Альтернативой написанию собственного десериализатора является использование класса RuntimeTypeAdapterFactory , присутствующего в исходном коде Gson . Однако она не предоставляется библиотекой для использования пользователем . Следовательно, нам нужно будет создать копию класса в нашем проекте 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);
}

Обратите внимание, что основной механизм остается прежним.

Нам все еще нужно ввести информацию о типе во время сериализации. Информация о типе может быть позже использована во время десериализации. Следовательно, тип поля по- прежнему требуется в каждом классе, чтобы это решение работало. `` Нам просто не нужно писать собственный десериализатор.

RuntimeTypeAdapterFactory предоставляет правильный адаптер типа на основе переданного ему имени поля и зарегистрированных подтипов.

4. Вывод

В этой статье мы увидели, как сериализовать и десериализовать список объектов с помощью Gson.

Как обычно, код доступен на GitHub .