Serialisieren und Deserialisieren einer Liste mit Gson

1. Einleitung

In diesem Tutorial werden einige erweiterte Fälle von Serialisierung und Deserialisierung für List mithilfe der Gson-Bibliothek von Google untersucht.

2. Liste der Objekte

Ein häufiger Anwendungsfall ist das Serialisieren und Deserialisieren einer Liste von POJOs.

Betrachten Sie die Klasse:

public class MyClass { private int id; private String name; public MyClass(int id, String name) { this.id = id; this.name = name; } // getters and setters }

So würden wir List serialisieren :

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

Wie wir sehen können, ist die Serialisierung ziemlich einfach.

Die Deserialisierung ist jedoch schwierig. Hier ist eine falsche Vorgehensweise:

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

Hier obwohl wir eine bekommen würde Liste der Größe zwei, post-Deserialisierung, wäre es nicht sein , Liste von MyClass . Daher löst Zeile 6 eine ClassCastException aus .

Gson kann eine Sammlung beliebiger Objekte serialisieren, die Daten jedoch nicht ohne zusätzliche Informationen deserialisieren. Dies liegt daran, dass der Benutzer den Typ des resultierenden Objekts nicht angeben kann. Stattdessen muss die Sammlung beim Deserialisieren von einem bestimmten generischen Typ sein.

Der richtige Weg, um die Liste zu deserialisieren, wäre:

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

Hier verwenden wir Gsons TypeToken , um den richtigen Typ zu bestimmen, der deserialisiert werden soll - ArrayList . Das zum Abrufen des listOfMyClassObject verwendete Idiom definiert tatsächlich eine anonyme lokale innere Klasse, die eine Methode getType () enthält , die den vollständig parametrisierten Typ zurückgibt.

3. Liste der polymorphen Objekte

3.1. Das Problem

Betrachten Sie eine beispielhafte Klassenhierarchie von Tieren:

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

Wie serialisieren und deserialisieren wir List ? Wir könnten TypeToken verwenden wie wir es im vorherigen Abschnitt verwendet haben. Gson kann den konkreten Datentyp der in der Liste gespeicherten Objekte jedoch immer noch nicht ermitteln.

3.2. Verwenden des benutzerdefinierten Deserializers

Eine Möglichkeit, dies zu lösen, besteht darin, dem serialisierten JSON Typinformationen hinzuzufügen. Wir berücksichtigen diese Typinformationen während der JSON-Deserialisierung. Dazu müssen wir unseren eigenen benutzerdefinierten Serializer und Deserializer schreiben.

Zunächst führen wir ein neues String- Feld mit dem Namen type in der Basisklasse Animal ein . Es speichert den einfachen Namen der Klasse, zu der es gehört.

Werfen wir einen Blick auf unsere Beispielklassen:

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 }

Die Serialisierung funktioniert weiterhin wie bisher ohne Probleme:

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

Um die Liste zu deserialisieren, müssen wir einen benutzerdefinierten Deserializer bereitstellen:

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

Hier behält die Zuordnung animalTypeRegistry die Zuordnung zwischen dem Klassennamen und dem Klassentyp bei.

Während der Deserialisierung extrahieren wir zuerst das neu hinzugefügte Typfeld . Mit diesem Wert suchen wir in der animalTypeRegistry- Map nach dem konkreten Datentyp. Dieser Datentyp wird dann an fromJson () übergeben .

Mal sehen, wie wir unseren benutzerdefinierten Deserializer verwenden:

@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. Verwenden von RuntimeTypeAdapterFactory

Eine Alternative zum Schreiben eines benutzerdefinierten Deserializers ist die Verwendung der im Gson-Quellcode enthaltenen RuntimeTypeAdapterFactory- Klasse. Allerdings ist es nicht von der Bibliothek für den Benutzer Gebrauch ausgesetzt . Daher müssen wir in unserem Java-Projekt eine Kopie der Klasse erstellen.

Sobald dies erledigt ist, können wir es verwenden, um unsere Liste zu deserialisieren:

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

Beachten Sie, dass der zugrunde liegende Mechanismus immer noch der gleiche ist.

Wir müssen die Typinformationen noch während der Serialisierung einführen. Die Typinformationen können später während der Deserialisierung verwendet werden. Daher ist der Feldtyp ist nach wie vor in jeder Klasse für diese Lösung zur Arbeit erforderlich. Wir müssen einfach keinen eigenen Deserializer schreiben.

RuntimeTypeAdapterFactory bietet den richtigen Typadapter basierend auf dem an ihn übergebenen Feldnamen und den registrierten Untertypen.

4. Fazit

In diesem Artikel haben wir gesehen, wie eine Liste von Objekten mit Gson serialisiert und deserialisiert wird.

Wie üblich ist der Code auf GitHub verfügbar.