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.