Arv med Jackson

1. Oversigt

I denne artikel vil vi se på at arbejde med klassehierarkier i Jackson.

To typiske anvendelsestilfælde er inkludering af subtype-metadata og ignorering af egenskaber nedarvet fra superklasser. Vi vil beskrive de to scenarier og et par omstændigheder, hvor der er behov for særlig behandling af undertyper.

2. Inkludering af undertypeoplysninger

Der er to måder at tilføje typeoplysninger ved serialisering og deserialisering af dataobjekter på, nemlig global standardindtastning og annoncer pr. Klasse.

2.1. Global standardindtastning

De følgende tre Java-klasser vil blive brugt til at illustrere global inkludering af typen metadata.

Køretøj superklasse:

offentlig abstrakt klasse Vehicle {private String make; privat streng model; beskyttet køretøj (String-mærke, String-model) {this.make = make; this.model = model; } // no-arg konstruktør, getters og setters}

Bil underklasse:

offentlig klasse bil udvider køretøj {privat int siddepladserCapacity; privat dobbelt topSpeed; public Car (String-mærke, String-model, int-siddekapacitet, dobbelt topSpeed) {super (mærke, model); this.seatingCapacity = siddepladsskapacitet; this.topSpeed ​​= topSpeed; } // no-arg konstruktør, getters og setters}

Lastbil underklasse:

offentlig klasse Truck udvider køretøj {privat dobbelt nyttelastCapacity; offentlig lastbil (streng mærke, streng model, dobbelt nyttelastCapacity) {super (mærke, model); this.payloadCapacity = nyttelastCapacity; } // no-arg konstruktør, getters og setters}

Global standardindtastning gør det muligt at erklære typeoplysninger kun én gang ved at aktivere det på en ObjectMapper objekt. Denne type metadata anvendes derefter på alle udpegede typer. Som et resultat er det meget praktisk at bruge denne metode til at tilføje typemetadata, især når der er et stort antal typer involveret. Ulempen er, at den bruger fuldt kvalificerede Java-typenavne som typeidentifikatorer og derfor er uegnet til interaktion med ikke-Java-systemer og kun kan anvendes til flere foruddefinerede typer.

Det Køretøj ovenfor vist struktur bruges til at udfylde en forekomst af Flåde klasse:

offentlig klasse Fleet {private List køretøjer; // getters og setters}

For at integrere type metadata skal vi aktivere skrivefunktionaliteten på ObjectMapper objekt, der senere vil blive brugt til serialisering og deserialisering af dataobjekter:

ObjectMapper.enableDefaultTyping (ObjectMapper.DefaultTyping anvendelighed, JsonTypeInfo.As includeAs)

Det anvendelighed parameter bestemmer de typer, der kræver typeoplysninger, og inkludererAs parameter er mekanismen til typemetadatainklusion. Derudover to andre varianter af aktivérDefaultTyping metoden leveres:

  • ObjectMapper.enableDefaultTyping (ObjectMapper.DefaultTyping anvendelighed): gør det muligt for den, der ringer op, at specificere anvendelighed, mens du bruger WRAPPER_ARRAY som standardværdi for inkludererAs
  • ObjectMapper.enableDefaultTyping (): anvendelser OBJECT_AND_NON_CONCRETE som standardværdi for anvendelighed og WRAPPER_ARRAY som standardværdi for inkludererAs

Lad os se, hvordan det fungerer. For at begynde med er vi nødt til at oprette en ObjectMapper objekt og aktiver standardindtastning på det:

ObjectMapper-kortlægger = ny ObjectMapper (); mapper.enableDefaultTyping ();

Det næste trin er at instantiere og udfylde datastrukturen, der blev introduceret i begyndelsen af ​​dette underafsnit. Koden til at gøre det genbruges senere i de efterfølgende underafsnit. Af hensyn til bekvemmelighed og genbrug navngiver vi det køretøjs instantieringsblok.

Bilbil = ny bil ("Mercedes-Benz", "S500", 5, 250.0); Lastbil = ny lastbil ("Isuzu", "NQR", 7500.0); Liste køretøjer = ny ArrayList (); køretøjer. tilføj (bil); køretøjer. tilføj (lastbil); Fleet serializedFleet = ny flåde (); serializedFleet.setVehicles (køretøjer);

Disse befolkede objekter vil derefter blive serieliseret:

String jsonDataString = mapper.writeValueAsString (serializedFleet);

Den resulterende JSON-streng:

{"køretøjer": ["java.util.ArrayList", [["org.baeldung.jackson.inheritance.Car", {"make": "Mercedes-Benz", "model": "S500", "sittingCapacity" : 5, "topSpeed": 250.0}], ["org.baeldung.jackson.inheritance.Truck", {"make": "Isuzu", "model": "NQR", "payloadCapacity": 7500.0}]]] }

Under deserialisering gendannes objekter fra JSON-strengen med typedata bevaret:

Flåde deserializedFleet = mapper.readValue (jsonDataString, Fleet.class);

De genskabte objekter vil være de samme konkrete undertyper, som de var før serialisering:

assertThat (deserializedFleet.getVehicles (). get (0), instanceOf (Car.class)); assertThat (deserializedFleet.getVehicles (). get (1), instanceOf (Truck.class));

2.2. Annoteringer pr. Klasse

Annotering pr. Klasse er en effektiv metode til at inkludere typeoplysninger og kan være meget nyttig i komplekse anvendelsestilfælde, hvor et betydeligt niveau af tilpasning er nødvendigt. Dette kan dog kun opnås på bekostning af komplikationer. Annoteringer pr. Klasse tilsidesætter global standardtypning, hvis typeoplysninger er konfigureret på begge måder.

For at gøre brug af denne metode skal supertypen kommenteres med @JsonTypeInfo og flere andre relevante kommentarer. Dette underafsnit bruger en datamodel svarende til Køretøj struktur i det foregående eksempel for at illustrere pr. klasse-kommentarer. Den eneste ændring er tilføjelsen af ​​kommentarer til Køretøj abstrakt klasse, som vist nedenfor:

@JsonTypeInfo (use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type") @JsonSubTypes ({@Type (value = Car.class, name = "car"), @Type (value = Truck.class, name = "truck")}) offentlig abstrakt klasse Køretøj {// felter, konstruktører, getters og settere}

Dataobjekter oprettes ved hjælp af køretøjs instantieringsblok introduceret i det foregående underafsnit og derefter serialiseret:

String jsonDataString = mapper.writeValueAsString (serializedFleet);

Serialiseringen producerer følgende JSON-struktur:

{"køretøjer": [{"type": "bil", "make": "Mercedes-Benz", "model": "S500", "sittingCapacity": 5, "topSpeed": 250.0}, {"type" : "truck", "make": "Isuzu", "model": "NQR", "payloadCapacity": 7500.0}]}

Denne streng bruges til at genskabe dataobjekter:

Flåde deserializedFleet = mapper.readValue (jsonDataString, Fleet.class);

Endelig valideres hele fremskridt:

assertThat (deserializedFleet.getVehicles (). get (0), instanceOf (Car.class)); assertThat (deserializedFleet.getVehicles (). get (1), instanceOf (Truck.class));

3. Ignorer egenskaber fra en supertype

Nogle gange skal nogle egenskaber nedarvet fra superklasser ignoreres under serialisering eller deserialisering. Dette kan opnås ved hjælp af en af ​​tre metoder: annoteringer, mix-ins og annotationsintrospektion.

3.1. Kommentarer

Der er to almindeligt anvendte Jackson-kommentarer til at ignorere egenskaber, hvilket er @JsonIgnorer og @JsonIgnoreProperties. Førstnævnte anvendes direkte til typemedlemmer, idet han beder Jackson om at ignorere den tilsvarende egenskab, når den serialiseres eller deserialiseres. Sidstnævnte bruges på ethvert niveau, inklusive type og type medlem, til at liste egenskaber, der skal ignoreres.

@JsonIgnoreProperties er stærkere end den anden, da det giver os mulighed for at ignorere egenskaber, der er nedarvet fra supertyper, som vi ikke har kontrol over, såsom typer i et eksternt bibliotek. Derudover giver denne kommentar os mulighed for at ignorere mange egenskaber på én gang, hvilket i nogle tilfælde kan føre til mere forståelig kode.

Følgende klassestruktur bruges til at demonstrere brug af kommentarer:

offentlig abstrakt klasse Vehicle {private String make; privat streng model; beskyttet køretøj (String-mærke, String-model) {this.make = make; this.model = model; } // no-arg constructor, getters and setters} @JsonIgnoreProperties ({"model", "sittingCapacity"}) offentlig abstrakt klasse Bil udvider Vehicle {private int seatingCapacity; @JsonIgnorer privat dobbelt topSpeed; beskyttet bil (String-mærke, String-model, int-siddekapacitet, dobbelt topSpeed) {super (mærke, model); this.seatingCapacity = siddekapacitet; this.topSpeed ​​= topSpeed; } // no-arg constructor, getters and setters} public class Sedan udvider Car {public Sedan (String make, String model, int seatingCapacity, double topSpeed) {super (make, model, sittingCapacity, topSpeed); } // no-arg constructor} offentlig klasse Crossover udvider bil {privat dobbelt bugseringsevne; kapacitet; public Crossover (String-mærke, String-model, int-siddekapacitet, dobbelt topSpeed, dobbelt towing-kapacitet) {super (mærke, model, siddekapacitet, topSpeed); this.towingCapacity = towingCapacity; } // no-arg konstruktør, getters og setters}

Som du kan se, @JsonIgnorer fortæller Jackson at ignorere Car.topSpeed ejendom, mens @JsonIgnoreProperties ignorerer Køretøjsmodel og Car.seatingCapacity dem.

Begge kommentarers opførsel valideres ved følgende test. Først skal vi instantere ObjectMapper og dataklasser, brug derefter det ObjectMapper forekomst for at serieisere dataobjekter:

ObjectMapper-kortlægger = ny ObjectMapper (); Sedan sedan = ny Sedan ("Mercedes-Benz", "S500", 5, 250.0); Crossover crossover = ny Crossover ("BMW", "X6", 5, 250.0, 6000.0); Liste køretøjer = ny ArrayList (); køretøjer. tilføjelse (sedan); køretøjer. tilføj (crossover); String jsonDataString = mapper.writeValueAsString (køretøjer);

jsonDataString indeholder følgende JSON-array:

[{"make": "Mercedes-Benz"}, {"make": "BMW", "towingCapacity": 6000.0}]

Endelig vil vi bevise tilstedeværelsen eller fraværet af forskellige ejendomsnavne i den resulterende JSON-streng:

assertThat (jsonDataString, containString ("make")); assertThat (jsonDataString, ikke (indeholderString ("model"))); assertThat (jsonDataString, ikke (indeholderString ("sittingCapacity"))); assertThat (jsonDataString, ikke (indeholderString ("topSpeed"))); assertThat (jsonDataString, containString ("towingCapacity"));

3.2. Mix-ins

Mix-ins giver os mulighed for at anvende adfærd (såsom at ignorere egenskaber ved serialisering og deserialisering) uden behov for direkte at anvende kommentarer til en klasse. Dette er især nyttigt, når vi beskæftiger os med tredjepartsklasser, hvor vi ikke kan ændre koden direkte.

Dette underafsnit genbruger den klassearvekæde, der blev introduceret i den foregående, bortset fra at @JsonIgnorer og @JsonIgnoreProperties bemærkninger om Bil klasse er fjernet:

offentlig abstrakt klasse Bil udvider køretøj {privat int sædeKapacitet; privat dobbelt topSpeed; // felter, konstruktører, getters og settere}

For at demonstrere operationer af mix-ins vil vi ignorere Vehicle.make og Car.topSpeed egenskaber, og brug derefter en test for at sikre, at alt fungerer som forventet.

Det første trin er at erklære en mix-in type:

privat abstrakt klasse CarMixIn {@JsonIgnorer offentligt strengemærke; @JsonIgnorer offentlig streng topSpeed; }

Dernæst er mix-in bundet til en dataklasse gennem en ObjectMapper objekt:

ObjectMapper-kortlægger = ny ObjectMapper (); mapper.addMixIn (Car.class, CarMixIn.class);

Derefter instantierer vi dataobjekter og serialiserer dem til en streng:

Sedan sedan = ny Sedan ("Mercedes-Benz", "S500", 5, 250.0); Crossover crossover = ny Crossover ("BMW", "X6", 5, 250.0, 6000.0); Liste køretøjer = ny ArrayList (); køretøjer. tilføjelse (sedan); køretøjer. tilføj (crossover); String jsonDataString = mapper.writeValueAsString (køretøjer);

jsonDataString indeholder nu følgende JSON:

[{"model": "S500", "sittingCapacity": 5}, {"model": "X6", "sittingCapacity": 5, "towingCapacity": 6000.0}]

Lad os endelig kontrollere resultatet:

assertThat (jsonDataString, ikke (indeholderString ("make"))); assertThat (jsonDataString, containString ("model")); assertThat (jsonDataString, containString ("sittingCapacity")); assertThat (jsonDataString, ikke (indeholderString ("topSpeed")); assertThat (jsonDataString, containString ("towingCapacity"));

3.3. Annotation Introspection

Annotationsintrospektion er den mest kraftfulde metode til at ignorere supertypeegenskaber, da det giver mulighed for detaljeret tilpasning ved hjælp af AnnotationIntrospector.hasIgnoreMarker API.

Dette underafsnit bruger det samme klassehierarki som det foregående. I dette brugssag vil vi bede Jackson om at ignorere Køretøjsmodel, Crossover.towingCapacity og alle ejendomme deklareret i Bil klasse. Lad os starte med erklæringen om en klasse, der udvider Jackson Annotation Introspector grænseflade:

klasse IgnoranceIntrospector udvider JacksonAnnotationIntrospector {public boolean hasIgnoreMarker (AnnotatedMember m)}

Introspektøren ignorerer alle egenskaber (det vil sige, den behandler dem som om de var markeret som ignoreret via en af ​​de andre metoder), der matcher det sæt betingelser, der er defineret i metoden.

Det næste trin er at registrere en forekomst af Uvidenhed Introspektor klasse med en ObjectMapper objekt:

ObjectMapper-kortlægger = ny ObjectMapper (); mapper.setAnnotationIntrospector (ny IgnoranceIntrospector ());

Nu opretter og serierer vi dataobjekter på samme måde som i afsnit 3.2. Indholdet af den nyproducerede streng er:

[{"make": "Mercedes-Benz"}, {"make": "BMW"}]

Endelig skal vi kontrollere, at introspektøren fungerede som beregnet:

assertThat (jsonDataString, containString ("make")); assertThat (jsonDataString, ikke (indeholderString ("model"))); assertThat (jsonDataString, ikke (indeholderString ("sittingCapacity"))); assertThat (jsonDataString, ikke (indeholderString ("topSpeed"))); assertThat (jsonDataString, ikke (indeholderString ("towingCapacity")));

4. Undertypehåndteringsscenarier

Dette afsnit vil behandle to interessante scenarier, der er relevante for håndtering af underklasser.

4.1. Konvertering mellem undertyper

Jackson tillader, at et objekt konverteres til en anden type end den oprindelige. Faktisk kan denne konvertering ske blandt alle kompatible typer, men det er mest nyttigt, når det bruges mellem to undertyper af samme interface eller klasse for at sikre værdier og funktionalitet.

For at demonstrere konvertering af en type til en anden, vil vi genbruge Køretøj hierarki taget fra afsnit 2 med tilføjelse af @JsonIgnorer anmærkning om ejendomme i Bil og Lastbil for at undgå inkompatibilitet.

offentlig klasse bil udvider køretøj {@JsonIgnorer privat int sædeKapacitet; @JsonIgnorer privat dobbelt topSpeed; // konstruktører, getters og settere} offentlig klasse Truck udvider køretøj {@JsonIgnorer privat dobbelt nyttelastCapacity; // konstruktører, getters og settere}

Følgende kode kontrollerer, at en konvertering er vellykket, og at det nye objekt bevarer dataværdier fra det gamle:

ObjectMapper-kortlægger = ny ObjectMapper (); Bilbil = ny bil ("Mercedes-Benz", "S500", 5, 250.0); Lastbil = mapper.convertValue (bil, lastbil.klasse); assertEquals ("Mercedes-Benz", truck.getMake ()); assertEquals ("S500", truck.getModel ());

4.2. Deserialisering uden No-arg konstruktører

Som standard genskaber Jackson dataobjekter ved hjælp af no-arg konstruktører. Dette er ubelejligt i nogle tilfælde, som når en klasse har ikke-standardkonstruktører, og brugerne skal skrive no-arg-dem bare for at tilfredsstille Jacksons krav. Det er endnu mere besværligt i et klassehierarki, hvor en no-arg-konstruktør skal føjes til en klasse og alle dem, der er højere i arvekæden. I disse tilfælde skabermetoder komme til undsætning.

Dette afsnit bruger en objektstruktur svarende til den i afsnit 2 med nogle ændringer til konstruktører. Specifikt er alle no-arg konstruktører droppet, og konstruktører af konkrete undertyper er kommenteret med @JsonCreator og @JsonProperty at gøre dem til skabermetoder.

offentlig klasse bil udvider køretøj {@JsonCreator offentlig bil (@JsonProperty ("make") String-mærke, @JsonProperty ("model") String-model, @JsonProperty ("siddepladser") int sædeCapacity, @JsonProperty ("topSpeed") dobbelt topSpeed ) {super (mærke, model); this.seatingCapacity = siddepladsskapacitet; this.topSpeed ​​= topSpeed; } // felter, getters og setters} offentlig klasse Truck udvider køretøj {@JsonCreator public Truck (@JsonProperty ("make") String make, @JsonProperty ("model") Strengmodel, @JsonProperty ("nyttelast") dobbelt nyttelastCapacity) {super (mærke, model); this.payloadCapacity = nyttelastCapacity; } // felter, getters og setters}

En test vil kontrollere, at Jackson kan håndtere objekter, der mangler no-arg konstruktører:

ObjectMapper-kortlægger = ny ObjectMapper (); mapper.enableDefaultTyping (); Bilbil = ny bil ("Mercedes-Benz", "S500", 5, 250.0); Lastbil = ny lastbil ("Isuzu", "NQR", 7500.0); Liste køretøjer = ny ArrayList (); køretøjer. tilføj (bil); køretøjer. tilføj (lastbil); Fleet serializedFleet = ny flåde (); serializedFleet.setVehicles (køretøjer); String jsonDataString = mapper.writeValueAsString (serializedFleet); mapper.readValue (jsonDataString, Fleet.class);

5. Konklusion

Denne tutorial har dækket adskillige interessante brugssager for at demonstrere Jacksons støtte til typen arv med fokus på polymorfisme og uvidenhed om supertype egenskaber.

Implementeringen af ​​alle disse eksempler og kodestykker kan findes i et GitHub-projekt.


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