Serialisere og deserialisere en liste med Gson

1. Introduksjon

I denne opplæringen vil vi utforske noen avanserte saker om serialisering og deserialisering for Liste ved hjelp av Googles Gson-bibliotek.

2. Liste over objekter

En vanlig brukssak er å serieisere og deserialisere en liste over POJOer.

Tenk på klassen:

offentlig klasse MyClass {private int id; privat strengnavn; offentlig MyClass (int id, strengnavn) {this.id = id; this.name = navn; } // getters og setters}

Slik serier vi Liste:

@Test offentlig ugyldig givenListOfMyClass_whenSerializing_thenCorrect () {List list = Arrays.asList (new MyClass (1, "name1"), new MyClass (2, "name2")); Gson gson = ny Gson (); String jsonString = gson.toJson (liste); Streng forventetString = "[{\" id \ ": 1, \" navn \ ": \" navn1 \ "}, {\" id \ ": 2, \" navn \ ": \" navn2 \ "}]" ; assertEquals (expectString, jsonString); }

Som vi kan se, er serialisering ganske grei.

Deserialisering er imidlertid vanskelig. Her er en feil måte å gjøre det på:

@Test (forventet = ClassCastException.class) offentlig ugyldig gittJsonString_whenIncorrectDeserializing_thenThrowClassCastException () {String inputString = "[{\" id \ ": 1, \" name \ ": \" name1 \ "}, {\" id \ ": 2 , \ "name \": \ "name2 \"}] "; Gson gson = ny Gson (); Liste outputList = gson.fromJson (inputString, ArrayList.class); assertEquals (1, outputList.get (0) .getId ()); }

Her, selv om vi ville få en Liste av størrelse to, etter deserialisering, ville det ikke være en Liste av Klassen min. Derfor kaster linje 6 ClassCastException.

Gson kan serieisere en samling av vilkårlige objekter, men kan ikke deserialisere dataene uten ytterligere informasjon. Det er fordi det ikke er noen måte for brukeren å indikere typen av det resulterende objektet. I stedet for, mens du deserialiserer, Samling må være av en spesifikk, generisk type.

Den riktige måten å deserialisere Liste ville vært:

@Test offentlig ugyldig gittJsonString_whenDeserializing_thenReturnListOfMyClass () {String inputString = "[{\" id \ ": 1, \" name \ ": \" name1 \ "}, {\" id \ ": 2, \" name \ ": \ "navn2 \"}] "; List inputList = Arrays.asList (new MyClass (1, "name1"), new MyClass (2, "name2")); Type listOfMyClassObject = ny TypeToken() {} .getType (); Gson gson = ny Gson (); Liste outputList = gson.fromJson (inputString, listOfMyClassObject); assertEquals (inputList, outputList); }

Her, vi bruker Gson's TypeToken for å bestemme riktig type som skal deserialiseres - ArrayList. Idiomet pleide å få listOfMyClassObject definerer faktisk en anonym lokal indre klasse som inneholder en metode getType () som returnerer den helt parameteriserte typen.

3. Liste over polymorfe objekter

3.1. Problemet

Tenk på et eksempel på klassehierarki av dyr:

offentlig abstrakt klasse Animal {// ...} public class Dog extends Animal {// ...} public class Cow extends Animal {// ...}

Hvordan serialiserer og deserialiserer vi Liste? Vi kan bruke TypeToken som vi brukte i forrige avsnitt. Imidlertid vil Gson fortsatt ikke kunne finne ut den konkrete datatypen til objektene som er lagret i listen.

3.2. Bruke Custom Deserializer

En måte å løse dette på er å legge til typeinformasjon i serien JSON. Vi respekterer den typen informasjon under JSON-deserialisering. For dette trenger vi å skrive vår egen tilpassede serialisering og deserializer.

For det første introduserer vi en ny String felt kalt type i basisklassen Dyr. Den lagrer det enkle navnet på klassen den tilhører.

La oss ta en titt på eksemplene våre på eksempler:

offentlig abstrakt klasse Animal {public String type = "Animal"; }
offentlig klasse Dog extends Animal {private String petName; offentlig hund () {petName = "Milo"; type = "Hund"; } // getters og setters}
offentlig klasse Cow utvider Animal {private String race; offentlig ku () {breed = "Jersey"; type = "Ku"; } // getters og setters}

Serialisering vil fortsette å fungere som før uten problemer:

@Test offentlig ugyldig gittPolymorphicList_whenSerializeWithTypeAdapter_thenCorrect () {String expectString = "[{\" petName \ ": \" Milo \ ", \" type \ ": \" Dog \ "}, {\" breed \ ": \" Jersey \ ", \" type \ ": \" Cow \ "}]"; Liste inList = ny ArrayList (); inList.add (ny hund ()); inList.add (ny ku ()); String jsonString = ny Gson (). ToJson (inList); assertEquals (expectString, jsonString); }

For å deserialisere listen, må vi tilby en tilpasset deserializer:

offentlig klasse AnimalDeserializer implementerer JsonDeserializer {private String animalTypeElementName; privat Gson gson; privat kart animalTypeRegistry; public AnimalDeserializer (String animalTypeElementName) {this.animalTypeElementName = animalTypeElementName; this.gson = nye Gson (); this.animalTypeRegistry = ny 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); Klasse animalType = animalTypeRegistry.get (animalTypeElement.getAsString ()); returner gson.fromJson (animalObject, animalType); }}

Her, den animalTypeRegistry kartet vedlikeholder kartleggingen mellom klassenavnet og klassetypen.

Under deserialisering trekker vi først ut det nylig tilføyde type felt. Ved å bruke denne verdien ser vi på animalTypeRegistry kart for å få den konkrete datatypen. Denne datatypen overføres deretter til fraJson ().

La oss se hvordan vi bruker vår tilpassede deserializer:

@Test offentlig ugyldighet gittPolymorphicList_whenDeserializeWithTypeAdapter_thenCorrect () {String inputString = "[{\" petName \ ": \" Milo \ ", \" type \ ": \" Dog \ "}, {\" breed \ ": \" Jersey \ ", \" type \ ": \" Cow \ "}]"; AnimalDeserializer deserializer = ny AnimalDeserializer ("type"); deserializer.registerBarnType ("Dog", Dog.class); deserializer.registerBarnType ("Cow", Cow.class); Gson gson = ny GsonBuilder () .registerTypeAdapter (Animal.class, deserializer) .create (); Liste outList = gson.fromJson (inputString, ny TypeToken() {}. getType ()); assertEquals (2, outList.size ()); assertTrue (outList.get (0) forekomst av hund); assertTrue (outList.get (1) instance of Cow); }

3.3. Ved hjelp av RuntimeTypeAdapterFactory

Et alternativ til å skrive en tilpasset deserializer er å bruke RuntimeTypeAdapterFactory klasse til stede i Gson-kildekoden. Derimot, det er ikke eksponert av biblioteket for brukeren å bruke. Derfor må vi lage en kopi av klassen i Java-prosjektet vårt.

Når dette er gjort, kan vi bruke den til å deserialisere listen vår:

@Test offentlig ugyldighet gittPolymorphicList_whenDeserializeWithRuntimeTypeAdapter_thenCorrect () {String inputString = "[{\" petName \ ": \" Milo \ ", \" type \ ": \" Dog \ "}, {\" breed \ ": \" Jersey \ ", \" type \ ": \" Cow \ "}]"; Type listOfAnimals = ny TypeToken() {}. getType (); RuntimeTypeAdapterFactory adapter = RuntimeTypeAdapterFactory.of (Animal.class, "type") .registerSubtype (Dog.class) .registerSubtype (Cow.class); Gson gson = ny GsonBuilder (). RegisterTypeAdapterFactory (adapter) .create (); List outList = gson.fromJson (inputString, listOfAnimals); assertEquals (2, outList.size ()); assertTrue (outList.get (0) forekomst av hund); assertTrue (outList.get (1) forekomst av ku); }

Merk at den underliggende mekanismen fortsatt er den samme.

Vi trenger fortsatt å introdusere typeinformasjonen under serialisering. Typeinformasjonen kan senere brukes under deserialisering. Derfor feltet type kreves fortsatt i hver klasse for at denne løsningen skal fungere. Vi trenger bare ikke å skrive vår egen deserializer.

RuntimeTypeAdapterFactory gir riktig type adapter basert på feltnavnet som sendes til det og de registrerte undertypene.

4. Konklusjon

I denne artikkelen så vi hvordan vi kan serieisere og deserialisere en liste over objekter ved hjelp av Gson.

Som vanlig er koden tilgjengelig på GitHub.


$config[zx-auto] not found$config[zx-overlay] not found