Serijalizacija i deserijalizacija popisa s Gson-om

1. Uvod

U ovom uputstvu istražit ćemo nekoliko slučajeva napredne serializacije i deserializacije za Popis koristeći Googleovu Gson knjižnicu.

2. Popis objekata

Jedan od uobičajenih slučajeva je serializacija i deserializacija popisa POJO-a.

Razmotrite razred:

javna klasa MyClass {private int id; privatni naziv niza; javni MyClass (int id, naziv niza) {this.id = id; this.name = ime; } // geteri i postavljači}

Evo kako bismo se serizirali Popis:

@Test javna praznina givenListOfMyClass_whenSerializing_thenCorrect () {Lista popisa = Arrays.asList (novi MyClass (1, "name1"), novi MyClass (2, "name2")); Gson gson = novi Gson (); Niz jsonString = gson.toJson (popis); Niz očekujeString = "[{\" id \ ": 1, \" name \ ": \" name1 \ "}, {\" id \ ": 2, \" name \ ": \" name2 \ "}]" ; assertEquals (očekivaniString, jsonString); }

Kao što vidimo, serializacija je prilično jednostavna.

Međutim, deserializacija je nezgodna. Evo pogrešnog načina:

@Test (očekuje se = ClassCastException.class) javna praznina givenJsonString_whenIncorrectDeserializing_thenThrowClassCastException () {String inputString = "[{\" id \ ": 1, \" name \ ": \" name1 \ "}, {\" id \ ": 2 , \ "ime \": \ "ime2 \"}] "; Gson gson = novi Gson (); Popis outputList = gson.fromJson (inputString, ArrayList.class); assertEquals (1, outputList.get (0) .getId ()); }

Ovdje, iako bismo dobili a Popis druge veličine, post-deserializacija, to ne bi bilo Popis od Moj razred. Stoga, linija # 6 baca ClassCastException.

Gson može serijalizirati zbirku proizvoljnih objekata, ali ne može deserijalizirati podatke bez dodatnih informacija. To je zato što korisnik ne može navesti vrstu rezultirajućeg objekta. Umjesto toga, dok je deserializirao, Kolekcija mora biti određenog, generičkog tipa.

Ispravan način deserijalizacije datoteke Popis bilo bi:

@Test javna praznina danaJsonString_whenDeserializing_thenReturnListOfMyClass () {String inputString = "[{\" id \ ": 1, \" name \ ": \" name1 \ "}, {\" id \ ": 2, \" name \ ": \ "name2 \"}] "; Popis inputList = Arrays.asList (novi MyClass (1, "name1"), novi MyClass (2, "name2")); Type listOfMyClassObject = novi TypeToken() {} .getType (); Gson gson = novi Gson (); Popis outputList = gson.fromJson (inputString, listOfMyClassObject); assertEquals (inputList, outputList); }

Ovdje, koristimo se Gsonovim TypeToken za određivanje ispravnog tipa koji će se deserijalizirati - ArrayList. Idiom koji se nekada dobivao listOfMyClassObject zapravo definira anonimnu lokalnu unutarnju klasu koja sadrži metodu getType () koji vraća potpuno parametrizirani tip.

3. Popis polimorfnih predmeta

3.1. Problem

Razmotrimo primjer hijerarhije klasa životinja:

javna apstraktna klasa Animal {// ...} javna klasa Pas produžuje Animal {// ...} javna klasa Krava produžuje Animal {// ...}

Kako se serializiramo i deserializiramo Popis? Mogli bismo koristiti TypeToken kao što smo koristili u prethodnom odjeljku. Međutim, Gson još uvijek neće moći shvatiti konkretnu vrstu podataka objekata pohranjenih na popisu.

3.2. Korištenje prilagođenog deserijalizatora

Jedan od načina da se to riješi je dodavanje podataka o tipu u serializirani JSON. Poštujemo tu vrstu podataka tijekom JSON-ove deserializacije. Za to moramo napisati vlastiti prilagođeni serializator i deserializator.

Prvo ćemo predstaviti novo Niz polje pozvano tip u osnovnoj klasi Životinja. Pohranjuje jednostavan naziv klase kojoj pripada.

Pogledajmo naše primjere klasa:

javni sažetak klase Animal {public String type = "Animal"; }
pas javne klase proširuje Animal {private String petName; javni Pas () {petName = "Milo"; type = "Pas"; } // geteri i postavljači}
krava javne klase proširuje životinju {privatna gudačka pasmina; javna Krava () {pasma = "Jersey"; type = "Krava"; } // geteri i postavljači}

Serijalizacija će i dalje raditi kao i prije, bez ikakvih problema:

@Test javna praznina givenPolymorphicList_whenSerializeWithTypeAdapter_thenCorrect () {String očekujeString = "[{\" petName \ ": \" Milo \ ", \" type \ ": \" Dog \ "}, {\" pasmina \ ": \" Jersey \ ", \" type \ ": \" Krava \ "}]"; Popis inList = novi ArrayList (); inList.add (novi Pas ()); inList.add (nova Krava ()); Niz jsonString = novi Gson (). ToJson (inList); assertEquals (očekivaniString, jsonString); }

Da bismo deserializirali popis, morat ćemo osigurati prilagođeni deserijalizator:

javna klasa AnimalDeserializer implementira JsonDeserializer {private String animalTypeElementName; privatni Gson gson; privatna karta animalTypeRegistry; javni AnimalDeserializer (Niz animalTypeElementName) {this.animalTypeElementName = animalTypeElementName; this.gson = novi Gson (); this.animalTypeRegistry = nova HashMap (); } javna void registerBarnType (niz animalTypeName, klasa animalType) {animalTypeRegistry.put (animalTypeName, animalType); } javna životinjska deserijalizacija (JsonElement json, Tip typeOfT, JsonDeserializationContext context) {JsonObject animalObject = json.getAsJsonObject (); JsonElement animalTypeElement = animalObject.get (animalTypeElementName); Klasa animalType = animalTypeRegistry.get (animalTypeElement.getAsString ()); vratiti gson.fromJson (animalObject, animalType); }}

Evo, animalTypeRegistry map održava mapiranje između naziva klase i tipa klase.

Tijekom deserializacije prvo izdvajamo novo dodano tip polje. Koristeći ovu vrijednost, pretražujemo animalTypeRegistry karta za dobivanje konkretnog tipa podataka. Zatim se ova vrsta podataka prosljeđuje na odJson ().

Pogledajmo kako koristiti naš prilagođeni deserijalizator:

@Test javna praznina danaPolymorphicList_whenDeserializeWithTypeAdapter_thenCorrect () {String inputString = "[{\" petName \ ": \" Milo \ ", \" type \ ": \" Dog \ "}, {\" pasmina \ ": \" Jersey \ ", \" type \ ": \" Krava \ "}]"; AnimalDeserializer deserializer = novi AnimalDeserializer ("tip"); deserializer.registerBarnType ("Pas", Dog.class); deserializer.registerBarnType ("Krava", klasa krava); Gson gson = novi GsonBuilder () .registerTypeAdapter (Animal.class, deserializer) .create (); Popis outList = gson.fromJson (inputString, novi TypeToken() {}. getType ()); assertEquals (2, outList.size ()); assertTrue (outList.get (0) instance Dog); assertTrue (outList.get (1) instanca krave); }

3.3. Koristeći RuntimeTypeAdapterFactory

Alternativa pisanju prilagođenog deserializatora je upotreba RuntimeTypeAdapterFactory klasa prisutna u izvornom kodu Gson. Međutim, knjižnica ga ne izlaže korisniku da bi ga mogao koristiti. Stoga ćemo u našem Java projektu morati stvoriti kopiju klase.

Nakon što je to učinjeno, možemo ga koristiti za deserijaliziranje popisa:

@Test javna praznina danaPolymorphicList_whenDeserializeWithRuntimeTypeAdapter_thenCorrect () {String inputString = "[{\" petName \ ": \" Milo \ ", \" type \ ": \" Dog \ "}, {\" pasmina \ ": \" Jersey \ ", \" type \ ": \" Krava \ "}]"; Type listOfAnimals = novi TypeToken() {}. getType (); RuntimeTypeAdapterFactory adapter = RuntimeTypeAdapterFactory.of (Animal.class, "type") .registerSubtype (Dog.class) .registerSubtype (Cow.class); Gson gson = novi GsonBuilder (). RegisterTypeAdapterFactory (adapter) .create (); Popis outList = gson.fromJson (inputString, listOfAnimals); assertEquals (2, outList.size ()); assertTrue (outList.get (0) instance Dog); assertTrue (outList.get (1) instanca krave); }

Imajte na umu da je temeljni mehanizam i dalje isti.

Još uvijek moramo uvesti informacije o tipu tijekom serializacije. Podaci o tipu mogu se kasnije koristiti tijekom deserializacije. Dakle, polje tip još uvijek je potreban u svakom razredu da bi ovo rješenje uspjelo. Jednostavno ne moramo pisati vlastiti deserijalizator.

RuntimeTypeAdapterFactory pruža ispravni adapter tipa na temelju imena polja koje mu je proslijeđeno i registriranih podtipova.

4. Zaključak

U ovom smo članku vidjeli kako serijski i deserializirati popis objekata pomoću Gsona.

Kao i obično, kod je dostupan na GitHub-u.