Сериализиране и десериализиране на списък с Gson

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 }

Ето как бихме сериализирали Списък :

@Test public void givenListOfMyClass_whenSerializing_thenCorrect() { List 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 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 inputList = Arrays.asList(new MyClass(1, "name1"), new MyClass(2, "name2")); Type listOfMyClassObject = new TypeToken
    
     () {}.getType(); Gson gson = new Gson(); List outputList = gson.fromJson(inputString, listOfMyClassObject); assertEquals(inputList, outputList); }
    

Тук използваме TypeToken на Gson, за да определим правилния тип, който ще бъде десериализиран - ArrayList . Идиомът, използван за получаване на listOfMyClassObject, всъщност дефинира анонимен локален вътрешен клас, съдържащ метод getType (), който връща напълно параметризирания тип.

3. Списък на полиморфните обекти

3.1. Проблемът

Помислете за примерна йерархия на класове животни:

public abstract class Animal { // ... } public class Dog extends Animal { // ... } public class Cow extends Animal { // ... }

Как да сериализираме и десериализираме списъка ? Можем да използваме TypeToken както използвахме в предишния раздел. Въпреки това, 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 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 { private String animalTypeElementName; private Gson gson; private Map
    
      animalTypeRegistry; public AnimalDeserializer(String animalTypeElementName) { this.animalTypeElementName = animalTypeElementName; this.gson = new Gson(); this.animalTypeRegistry = new HashMap(); } public void registerBarnType(String animalTypeName, Class animalType) { animalTypeRegistry.put(animalTypeName, animalType); } public Animal deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) { JsonObject animalObject = json.getAsJsonObject(); JsonElement animalTypeElement = animalObject.get(animalTypeElementName); Class 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 outList = gson.fromJson(inputString, new TypeToken
    
     (){}.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
    
     (){}.getType(); RuntimeTypeAdapterFactory adapter = RuntimeTypeAdapterFactory.of(Animal.class, "type") .registerSubtype(Dog.class) .registerSubtype(Cow.class); Gson gson = new GsonBuilder().registerTypeAdapterFactory(adapter).create(); List 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.