Dedenie s Jacksonom
1. Prehľad
V tomto článku sa pozrieme na prácu s hierarchiami tried v Jacksonovi.
Dva typické prípady použitia sú zahrnutie metadát podtypu a ignorovanie vlastností zdedených zo superclassov. Popíšeme tieto dva scenáre a niekoľko okolností, keď je potrebné špeciálne zaobchádzanie s podtypmi.
2. Zahrnutie informácií o podtype
Existujú dva spôsoby, ako pridať informácie o type pri serializácii a deserializácii údajových objektov, a to globálne predvolené písanie a anotácie podľa jednotlivých tried.
2.1. Globálne predvolené písanie
Nasledujúce tri triedy Java budú použité na ilustráciu globálneho zahrnutia metadát typu.
Vozidlo nadtrieda:
verejná abstraktná trieda Vehicle {private String make; súkromný reťazcový model; chránené vozidlo (značka, model reťazca) {this.make = značka; this.model = model; } // konštruktor no-arg, getre a setre}
Auto podtrieda:
verejná trieda Auto rozširuje vozidlo {súkromné int sedenieKapacita; súkromné zdvojnásobenie rýchlosti; public Car (značka reťazca, model reťazca, int seatCapacity, dvojitý topSpeed) {super (značka, model); this.seatingCapacity = seatingCapacity; this.topSpeed = topSpeed; } // konštruktor no-arg, getre a setre}
Nákladné auto podtrieda:
Public Class Truck rozširuje Vehicle {private double payloadCapacity; public Truck (značka reťazca, model reťazca, dvojité užitočné zaťaženie) {{super (značka, model); this.payloadCapacity = payloadCapacity; } // konštruktor no-arg, getre a setre}
Globálne predvolené písanie umožňuje povoliť typové informácie iba raz tak, že ich povolíte v ObjectMapper objekt. Metadáta tohto typu sa potom použijú na všetky určené typy. Vo výsledku je veľmi vhodné použiť túto metódu na pridanie metadát typu, najmä ak sa jedná o veľké množstvo typov. Nevýhodou je, že ako identifikátory typov používa plne kvalifikované názvy typov Java, a preto je nevhodný na interakcie so systémami, ktoré nie sú Java, a je použiteľný iba pre niekoľko preddefinovaných druhov typov.
The Vozidlo štruktúra uvedená vyššie sa používa na vyplnenie inštancie Flotila trieda:
flotila verejnej triedy {vozidlá so súkromným zoznamom; // zakladatelia a zakladatelia}
Ak chcete vložiť metadáta typu, musíme povoliť funkčnosť písania v ObjectMapper objekt, ktorý sa neskôr použije na serializáciu a deserializáciu dátových objektov:
ObjectMapper.enableDefaultTyping (použiteľnosť ObjectMapper.DefaultTyping, JsonTypeInfo.As includeAs)
The použiteľnosť parameter určuje typy vyžadujúce typové informácie a zahrnúťAs parameter je mechanizmus na zahrnutie metadát typu. Dodatočne ďalšie dve varianty enableDefaultTyping sú poskytované:
- ObjectMapper.enableDefaultTyping (použiteľnosť ObjectMapper.DefaultTyping): umožňuje volajúcemu určiť použiteľnosť, pri používaní WRAPPER_ARRAY ako predvolená hodnota pre zahrnúťAs
- ObjectMapper.enableDefaultTyping (): používa OBJECT_AND_NON_CONCRETE ako predvolená hodnota pre použiteľnosť a WRAPPER_ARRAY ako predvolená hodnota pre zahrnúťAs
Pozrime sa, ako to funguje. Na začiatok si musíme vytvoriť ObjectMapper objekt a povoliť na ňom predvolené písanie:
ObjectMapper mapovač = nový ObjectMapper (); mapper.enableDefaultTyping ();
Ďalším krokom je vytvorenie inštancie a vyplnenie dátovej štruktúry zavedenej na začiatku tejto podkapitoly. Kód, ktorý to urobí, sa znova použije neskôr v ďalších pododdieloch. Kvôli pohodliu a opätovnému použitiu ho pomenujeme inštančný blok vozidla.
Automobil = nové auto („Mercedes-Benz“, „S500“, 5, 250,0); Nákladné vozidlo = nový nákladný automobil ("Isuzu", "NQR", 7500.0); Zoznam vozidiel = nový ArrayList (); vozidlá.pridat (auto); vozidlá.pridať (nákladné auto); Flotila serializedFleet = nová flotila (); serializedFleet.setVehicles (vozidlá);
Tieto vyplnené objekty sa potom serializujú:
Reťazec jsonDataString = mapper.writeValueAsString (serializedFleet);
Výsledný reťazec JSON:
{"vehicles": ["java.util.ArrayList", [["org.baeldung.jackson.inheritance.Car", {"make": "Mercedes-Benz", "model": "S500", "seatCapacity" : 5, "topSpeed": 250.0}], ["org.baeldung.jackson.inheritance.Truck", {"make": "Isuzu", "model": "NQR", "payloadCapacity": 7500.0}]]] }
Počas deserializácie sa objekty obnovia z reťazca JSON so zachovanými typovými údajmi:
Fleet deserializedFleet = mapper.readValue (jsonDataString, Fleet.class);
Znovu vytvorené objekty budú rovnaké konkrétne podtypy ako pred serializáciou:
assertThat (deserializedFleet.getVehicles (). get (0), instanceOf (Car.class)); assertThat (deserializedFleet.getVehicles (). get (1), instanceOf (Truck.class));
2.2. Anotácie pre jednotlivé triedy
Anotácia podľa tried je výkonná metóda na zahrnutie informácií o type a môže byť veľmi užitočná v zložitých prípadoch použitia, keď je nevyhnutná značná úroveň prispôsobenia. To sa však dá dosiahnuť iba na úkor komplikácií. Ak sú informácie o type nakonfigurované oboma spôsobmi, anotácie podľa tried prepíšu globálne predvolené písanie.
Na použitie tejto metódy by mal byť nadtyp anotovaný @JsonTypeInfo a niekoľko ďalších relevantných anotácií. V tejto podsekcii sa použije dátový model podobný Vozidlo štruktúra v predchádzajúcom príklade na ilustráciu anotácií pre jednotlivé triedy. Jedinou zmenou je pridanie anotácií na Vozidlo abstraktná trieda, ako je uvedené nižšie:
@JsonTypeInfo (use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type") @JsonSubTypes ({@Type (value = Car.class, name = "car"), @Type (value = Truck.class, name = "truck")}) verejná abstraktná trieda Vozidlo {// polia, konštruktéri, zakladatelia a zakladatelia}
Dátové objekty sa vytvárajú pomocou inštančný blok vozidla predstavené v predchádzajúcej podsekcii a potom serializované:
Reťazec jsonDataString = mapper.writeValueAsString (serializedFleet);
Serializácia vytvára nasledujúcu štruktúru JSON:
{"vehicles": [{"type": "car", "make": "Mercedes-Benz", "model": "S500", "seatCapacity": 5, "topSpeed": 250.0}, {"type" : "truck", "make": "Isuzu", "model": "NQR", "payloadCapacity": 7500.0}]}
Tento reťazec sa používa na opätovné vytvorenie údajových objektov:
Fleet deserializedFleet = mapper.readValue (jsonDataString, Fleet.class);
Nakoniec je celý postup validovaný:
assertThat (deserializedFleet.getVehicles (). get (0), instanceOf (Car.class)); assertThat (deserializedFleet.getVehicles (). get (1), instanceOf (Truck.class));
3. Ignorovanie vlastností zo supertypu
Niektoré vlastnosti zdedené zo superclassov je niekedy potrebné počas serializácie alebo deserializácie ignorovať. To je možné dosiahnuť jednou z troch metód: anotácie, kombinácie a introspekcia anotácií.
3.1. Anotácie
Na ignorovanie vlastností, ktoré sú, sa bežne používajú dve Jacksonove anotácie @JsonIgnore a @JsonIgnoreProperties. Prvý z nich sa priamo aplikuje na členov typu a povie Jacksonovi, aby pri serializácii alebo deserializácii ignoroval príslušnú vlastnosť. Posledná zmienka sa používa na akejkoľvek úrovni, vrátane typu a typového člena, na výpis vlastností, ktoré by sa mali ignorovať.
@JsonIgnoreProperties je výkonnejší ako ten druhý, pretože nám umožňuje ignorovať vlastnosti zdedené zo supertypov, nad ktorými nemáme kontrolu, napríklad typy v externej knižnici. Táto anotácia nám navyše umožňuje ignorovať veľa vlastností naraz, čo môže v niektorých prípadoch viesť k zrozumiteľnejšiemu kódu.
Na demonštráciu použitia anotácií sa používa nasledujúca štruktúra triedy:
verejná abstraktná trieda Vehicle {private String make; súkromný reťazcový model; chránené vozidlo (značka, model reťazca) {this.make = značka; this.model = model; } // konštruktor no-arg, getre a settery} @JsonIgnoreProperties ({"model", "seatCapacity"}) verejná abstraktná trieda Car rozširuje Vehicle {private int seatingCapacity; @JsonIgnore private double topSpeed; chránené auto (značka reťazca, model reťazca, int seatCapacity, dvojitý vrchol rýchlosti) {super (značka, model); this.seatingCapacity = seatingCapacity; this.topSpeed = topSpeed; } // no-arg constructor, getters and setters} public class Sedan extends Car {public Sedan (String make, String model, int seatingCapacity, double topSpeed) {super (make, model, seatingCapacity, topSpeed); } // no-arg constructor} verejná trieda Crossover rozširuje Car {private double towingCapacity; public Crossover (značka reťazca, model reťazca, int seatCapacity, dvojitý topSpeed, dvojitý príves), {super (značka, model, sedenieCapacity, topSpeed); this.towingCapacity = towingCapacity; } // konštruktor no-arg, getre a setre}
Ako môžeš vidieť, @JsonIgnore hovorí Jacksonovi, aby ho ignoroval Car.topSpeed majetku, zatiaľ čo @JsonIgnoreProperties ignoruje Vozidlo.model a Kapacita auta tie.
Správanie oboch anotácií je overené nasledujúcim testom. Najprv musíme urobiť inštanciu ObjectMapper a dátové triedy, potom to použite ObjectMapper inštancia na serializáciu dátových objektov:
ObjectMapper mapovač = nový ObjectMapper (); Sedan sedan = nový Sedan ("Mercedes-Benz", "S500", 5, 250,0); Crossover crossover = nový Crossover ("BMW", "X6", 5, 250,0, 6000,0); Zoznam vozidiel = nový ArrayList (); vozidlá.add (sedan); vozidlá.pridat (crossover); Reťazec jsonDataString = mapper.writeValueAsString (vozidlá);
jsonDataString obsahuje nasledujúce pole JSON:
[{"make": "Mercedes-Benz"}, {"make": "BMW", "towingCapacity": 6000.0}]
Nakoniec dokážeme prítomnosť alebo neprítomnosť rôznych mien vlastností vo výslednom reťazci JSON:
assertThat (jsonDataString, containsString ("make")); assertThat (jsonDataString, nie (containsString ("model"))); assertThat (jsonDataString, nie (containsString ("SedenieCapacity"))); assertThat (jsonDataString, nie (containsString ("topSpeed"))); assertThat (jsonDataString, containsString ("towingCapacity"));
3.2. Mix-ins
Mix-ins nám umožňujú aplikovať správanie (napríklad ignorovanie vlastností pri serializácii a deserializácii) bez potreby priameho použitia anotácií na triedu. To je užitočné najmä pri rokovaniach s triedami tretích strán, v ktorých nemôžeme priamo upravovať kód.
Táto podkapitola znovu používa triedny dedičský reťazec zavedený v predchádzajúcom, okrem toho, že @JsonIgnore a @JsonIgnoreProperties anotácie na Auto trieda boli odstránené:
verejná abstraktná trieda Auto rozširuje Vozidlo {private int seatingCapacity; súkromné zdvojnásobenie rýchlosti; // polia, konštruktory, getre a setre}
Za účelom demonštrácie operácií kombinácií budeme ignorovať Výroba vozidla a Car.topSpeed vlastnosti, potom pomocou testu overte, či všetko funguje podľa očakávaní.
Prvým krokom je vyhlásenie typu kombinácie:
súkromná abstraktná trieda CarMixIn {@JsonIgnore public Značka reťazca; @JsonIgnore public String topSpeed; }
Ďalej je mix viazaný na dátovú triedu prostredníctvom ObjectMapper objekt:
ObjectMapper mapovač = nový ObjectMapper (); mapper.addMixIn (Car.class, CarMixIn.class);
Potom inštancujeme dátové objekty a serializujeme ich do reťazca:
Sedan sedan = nový Sedan ("Mercedes-Benz", "S500", 5, 250,0); Crossover crossover = nový Crossover ("BMW", "X6", 5, 250,0, 6000,0); Zoznam vozidiel = nový ArrayList (); vozidlá.add (sedan); vozidlá.pridat (crossover); Reťazec jsonDataString = mapper.writeValueAsString (vozidlá);
jsonDataString teraz obsahuje nasledujúci JSON:
[{"model": "S500", "SeatCapacity": 5}, {"model": "X6", "SeatCapacity": 5, "TowingCapacity": 6000.0}]
Nakoniec overíme výsledok:
assertThat (jsonDataString, nie (containsString ("make"))); assertThat (jsonDataString, containsString ("model")); assertThat (jsonDataString, containsString ("sedíCapacity")); assertThat (jsonDataString, nie (containsString ("topSpeed"))); assertThat (jsonDataString, containsString ("towingCapacity"));
3.3. Anotácia Introspekcia
Introspekcia anotácií je najsilnejšia metóda na ignorovanie vlastností nadtypu, pretože umožňuje podrobné prispôsobenie pomocou AnnotationIntrospector.hasIgnoreMarker API.
Táto podkapitola využíva rovnakú hierarchiu tried ako predchádzajúca. V tomto prípade použitia požiadame Jacksona, aby ho ignoroval Vozidlo.model, Crossover.towingCapacity a všetky vlastnosti deklarované v Auto trieda. Začnime s vyhlásením triedy, ktorá rozširuje JacksonAnnotationIntrospector rozhranie:
trieda IgnoranceIntrospector rozširuje JacksonAnnotationIntrospector {public boolean hasIgnoreMarker (AnnotatedMember m)}
Introspektor bude ignorovať všetky vlastnosti (to znamená, že s nimi bude zaobchádzať, akoby boli označené ako ignorované pomocou jednej z ďalších metód), ktoré zodpovedajú množine podmienok definovaných v metóde.
Ďalším krokom je registrácia inštancie súboru IgnoranceIntrospector trieda s ObjectMapper objekt:
ObjectMapper mapovač = nový ObjectMapper (); mapper.setAnnotationIntrospector (nový IgnoranceIntrospector ());
Teraz vytvárame a serializujeme dátové objekty rovnakým spôsobom ako v časti 3.2. Obsah novo vyrobeného reťazca je:
[{"make": "Mercedes-Benz"}, {"make": "BMW"}]
Nakoniec overíme, či introspektor pracoval podľa očakávaní:
assertThat (jsonDataString, containsString ("make")); assertThat (jsonDataString, nie (containsString ("model"))); assertThat (jsonDataString, nie (containsString ("SedenieCapacity"))); assertThat (jsonDataString, nie (containsString ("topSpeed"))); assertThat (jsonDataString, nie (containsString ("towingCapacity")));
4. Scenáre spracovania podtypu
Táto časť sa bude zaoberať dvoma zaujímavými scenármi relevantnými pre spracovanie podtried.
4.1. Konverzia medzi podtypmi
Jackson umožňuje prevedenie objektu na iný typ, ako bol pôvodný. V skutočnosti sa táto konverzia môže vyskytnúť medzi ľubovoľnými kompatibilnými typmi, ale je najužitočnejšia, keď sa na zabezpečenie hodnôt a funkčnosti používa medzi dvoma podtypmi rovnakého rozhrania alebo triedy.
Aby sme demonštrovali konverziu typu na iný, znova použijeme Vozidlo hierarchia prevzatá z oddielu 2 s doplnením @JsonIgnore anotácia k nehnuteľnostiam v Auto a Nákladné auto aby sa zabránilo nekompatibilite.
verejná trieda Auto rozširuje vozidlo {@JsonIgnore private int seatingCapacity; @JsonIgnore private double topSpeed; // constructors, getters and setters} public class Truck extends Vehicle {@JsonIgnore private double payloadCapacity; // konštruktory, getre a setre}
Nasledujúci kód overí, či je konverzia úspešná a či nový objekt zachová hodnoty údajov zo starého:
ObjectMapper mapovač = nový ObjectMapper (); Automobil = nové auto („Mercedes-Benz“, „S500“, 5, 250,0); Nákladné vozidlo = mapper.convertValue (auto, Truck.class); assertEquals ("Mercedes-Benz", truck.getMake ()); assertEquals ("S500", truck.getModel ());
4.2. Deserializácia bez konštruktérov No-arg
V predvolenom nastavení Jackson znovu vytvára dátové objekty pomocou konštruktorov no-arg. To je v niektorých prípadoch nepohodlné, napríklad keď má trieda konštruktory, ktoré nie sú predvolené, a používatelia musia napísať tie, ktoré neobsahujú arg, aby vyhoveli Jacksonovým požiadavkám. Je to ešte ťažšie v hierarchii tried, kde musí byť do triedy a všetkých vyšších v reťazci dedičstva pridaný konštruktér no-arg. V týchto prípadoch metódy tvorcu prísť na záchranu.
Táto časť bude používať objektovú štruktúru podobnú štruktúre v časti 2 s niektorými zmenami v konštruktoroch. Konkrétne sú zrušené všetky konštruktory no-arg a konštruktéri konkrétnych podtypov sú anotovaní pomocou @JsonCreator a @JsonProperty aby boli metódami tvorcov.
public class Car extendes Vehicle {@JsonCreator public Car (@JsonProperty ("make") String make, @JsonProperty ("model") String model, @JsonProperty ("Sedenia") int SeatCapacity, @JsonProperty ("topSpeed") double topSpeed ) {super (značka, model); this.seatingCapacity = seatingCapacity; this.topSpeed = topSpeed; } // Fields, Getters and Setters} Public Class Truck extends Vehicle {@JsonCreator Public Truck (@JsonProperty ("make") String make, @JsonProperty ("model") String model, @JsonProperty ("payload") double payloadCapacity) {super (značka, model); this.payloadCapacity = payloadCapacity; } // polia, getre a setre}
Test overí, či si Jackson poradí s objektmi, ktorým chýbajú konštruktory bez arg:
ObjectMapper mapovač = nový ObjectMapper (); mapper.enableDefaultTyping (); Automobil = nové auto („Mercedes-Benz“, „S500“, 5, 250,0); Nákladné vozidlo = nový nákladný automobil ("Isuzu", "NQR", 7500.0); Zoznam vozidiel = nový ArrayList (); vozidlá.pridat (auto); vozidlá.pridať (nákladné auto); Flotila serializedFleet = nová flotila (); serializedFleet.setVehicles (vozidlá); Reťazec jsonDataString = mapper.writeValueAsString (serializedFleet); mapper.readValue (jsonDataString, Fleet.class);
5. Záver
Tento výukový program sa venoval niekoľkým zaujímavým prípadom použitia na demonštráciu Jacksonovej podpory pre typové dedenie so zameraním na polymorfizmus a neznalosť vlastností supertypu.
Implementáciu všetkých týchto príkladov a útržkov kódu nájdete v projekte GitHub.