Arv med Jackson

1. Oversikt

I denne artikkelen vil vi se på å jobbe med klassehierarkier i Jackson.

To typiske bruksområder er inkludering av subtype metadata og ignorering av egenskaper arvet fra superklasser. Vi skal beskrive de to scenariene og et par forhold der det er behov for spesiell behandling av undertyper.

2. Inkludering av undertypeinformasjon

Det er to måter å legge til typeinformasjon når du serialiserer og deserialiserer dataobjekter, nemlig global standardtyping og merknader per klasse.

2.1. Global standardtyping

Følgende tre Java-klasser vil bli brukt til å illustrere global inkludering av type metadata.

Kjøretøy superklasse:

offentlig abstrakt klasse Vehicle {private String make; privat streng modell; beskyttet kjøretøy (strengmerke, strengmodell) {this.make = make; this.model = modell; } // no-arg constructor, getters and setters}

Bil underklasse:

offentlig klasse Bil utvider kjøretøy {private int sitteplasserKapasitet; privat dobbel topphastighet; public Car (String merke, String modell, int sitteplasserKapasitet, dobbel toppSpeed) {super (merke, modell); this.seatingCapacity = sitteplasserCapacity; this.topSpeed ​​= topSpeed; } // no-arg konstruktør, getters og setters}

Lastebil underklasse:

offentlig klasse lastebil utvider kjøretøy {privat dobbelt nyttelastCapacity; offentlig lastebil (strengmerke, strengmodell, dobbel nyttelastkapasitet) {super (merke, modell); this.payloadCapacity = nyttelastCapacity; } // no-arg konstruktør, getters og setters}

Global standardtyping gjør at typeinformasjon kun kan deklareres en gang ved å aktivere den på en ObjectMapper gjenstand. Den typen metadata blir deretter brukt på alle angitte typer. Som et resultat er det veldig praktisk å bruke denne metoden for å legge til type metadata, spesielt når det er et stort antall typer involvert. Ulempen er at den bruker fullt kvalifiserte Java-typenavn som typeidentifikatorer, og er dermed uegnet for interaksjoner med ikke-Java-systemer, og er bare anvendelig på flere forhåndsdefinerte typer typer.

De Kjøretøy strukturen vist ovenfor brukes til å fylle ut en forekomst av Flåte klasse:

offentlig klasse Fleet {private List kjøretøy; // getters og setters}

For å legge inn type metadata, må vi aktivere skrivefunksjonaliteten på ObjectMapper objekt som skal brukes til serialisering og deserialisering av dataobjekter senere:

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

De anvendbarhet parameteren bestemmer typene som krever typeinformasjon, og inkludererAs parameter er mekanismen for inkludering av type metadata. I tillegg to andre varianter av enableDefaultTyping metoden er gitt:

  • ObjectMapper.enableDefaultTyping (ObjectMapper.DefaultTyping anvendbarhet): lar den som ringer spesifisere anvendbarhet, mens du bruker WRAPPER_ARRAY som standardverdi for inkludererAs
  • ObjectMapper.enableDefaultTyping (): bruker OBJECT_AND_NON_CONCRETE som standardverdi for anvendbarhet og WRAPPER_ARRAY som standardverdi for inkludererAs

La oss se hvordan det fungerer. For å begynne, må vi lage en ObjectMapper objekt og aktiver standard skriving på den:

ObjectMapper mapper = ny ObjectMapper (); mapper.enableDefaultTyping ();

Det neste trinnet er å instansiere og fylle ut datastrukturen som ble introdusert i begynnelsen av denne underdelen. Koden for å gjøre det vil bli brukt på nytt senere i de påfølgende delene. Av hensyn til bekvemmelighet og gjenbruk vil vi gi den navnet instantiering av kjøretøy.

Bilbil = ny bil ("Mercedes-Benz", "S500", 5, 250.0); Lastebil = ny lastebil ("Isuzu", "NQR", 7500.0); Liste kjøretøy = ny ArrayList (); vehicles.add (bil); vehicles.add (lastebil); Fleet serializedFleet = new Fleet (); serializedFleet.setVehicles (kjøretøy);

Disse befolkede objektene blir deretter seriellisert:

String jsonDataString = mapper.writeValueAsString (serializedFleet);

Den resulterende JSON-strengen:

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

Under deserialisering gjenopprettes objekter fra JSON-strengen med typedata bevart:

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

De gjenopprettede objektene vil være de samme konkrete undertypene som de var før serialisering:

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

2.2. Merknader per klasse

Merknad per klasse er en kraftig metode for å inkludere typeinformasjon og kan være veldig nyttig for komplekse bruksområder der et betydelig nivå av tilpasning er nødvendig. Dette kan imidlertid bare oppnås på bekostning av komplikasjoner. Merknader per klasse overstyrer global standardtyping hvis typeinformasjon er konfigurert på begge måter.

For å gjøre bruk av denne metoden, bør supertypen merkes med @JsonTypeInfo og flere andre relevante kommentarer. Dette underavsnittet vil bruke en datamodell som ligner på Kjøretøy struktur i forrige eksempel for å illustrere merknader per klasse. Den eneste endringen er tilføyelsen av merknader på Kjøretøy abstrakt klasse, som vist nedenfor:

@JsonTypeInfo (bruk = JsonTypeInfo.Id.NAME, inkluderer = JsonTypeInfo.As.PROPERTY, eiendom = "type") @JsonSubTypes ({@Type (verdi = Car.class, navn = "bil"), @Type (verdi = Truck.class, name = "truck")}) offentlig abstrakt klasse Vehicle {// felt, konstruktører, getters og setters}

Dataobjekter opprettes ved hjelp av øyeblikkelig blokkering av kjøretøy introdusert i forrige del, og deretter seriellisert:

String jsonDataString = mapper.writeValueAsString (serializedFleet);

Serialiseringen produserer følgende JSON-struktur:

{"vehicles": [{"type": "car", "make": "Mercedes-Benz", "model": "S500", "seatingCapacity": 5, "topSpeed": 250.0}, {"type" : "truck", "make": "Isuzu", "model": "NQR", "payloadCapacity": 7500.0}]}

Denne strengen brukes til å gjenopprette dataobjekter:

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

Til slutt er hele fremdriften validert:

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

3. Ignorer egenskaper fra en supertype

Noen ganger må noen egenskaper arvet fra superklasser ignoreres under serialisering eller deserialisering. Dette kan oppnås ved en av tre metoder: merknader, innblandinger og kommentar introspeksjon.

3.1. Kommentarer

Det er to ofte brukte Jackson-merknader for å ignorere egenskaper @JsonIgnore og @JsonIgnoreProperties. Førstnevnte brukes direkte på typemedlemmer, og forteller Jackson å ignorere den tilhørende egenskapen når du serialiserer eller deserialiserer. Sistnevnte brukes på alle nivåer, inkludert type og type medlem, for å liste egenskaper som skal ignoreres.

@JsonIgnoreProperties er kraftigere enn den andre siden den lar oss ignorere egenskaper som er arvet fra supertyper som vi ikke har kontroll over, for eksempel typer i et eksternt bibliotek. I tillegg tillater denne merknaden oss å ignorere mange egenskaper samtidig, noe som i noen tilfeller kan føre til mer forståelig kode.

Følgende klassestruktur brukes til å demonstrere bruk av merknader:

offentlig abstrakt klasse Vehicle {private String make; privat streng modell; beskyttet kjøretøy (strengmerke, strengmodell) {this.make = make; this.model = modell; } // no-arg constructor, getters and setters} @JsonIgnoreProperties ({"model", "seatingCapacity"}) offentlig abstrakt klasse Car extends Vehicle {private int seatingCapacity; @JsonIgnorer privat dobbel topSpeed; beskyttet bil (strengmerke, strengmodell, int sitteplass Kapasitet, dobbel topphastighet) {super (merke, modell); this.seatingCapacity = sitteplasserCapacity; this.topSpeed ​​= topSpeed; } // no-arg constructor, getters and setters} public class Sedan utvider Car {public Sedan (String make, String model, int seatingCapacity, double topSpeed) {super (make, model, sittingCapacity, topSpeed); } // no-arg constructor} offentlig klasse Crossover utvider Bil {privat dobbel slepingCapacity; public Crossover (String make, String model, int seatingCapacity, double topSpeed, double towingCapacity) {super (merke, modell, sittingCapacity, topSpeed); this.towingCapacity = towingCapacity; } // no-arg constructor, getters and setters}

Som du kan se, @JsonIgnore sier Jackson å ignorere Car.topSpeed eiendom, mens @JsonIgnoreProperties ignorerer Kjøretøy. Modell og Car.seatingCapacity de.

Oppførselen til begge kommentarene er validert av følgende test. Først må vi instantiere ObjectMapper og dataklasser, og bruk deretter det ObjectMapper forekomst for å serieisere dataobjekter:

ObjectMapper mapper = ny ObjectMapper (); Sedan sedan = ny Sedan ("Mercedes-Benz", "S500", 5, 250.0); Crossover crossover = ny Crossover ("BMW", "X6", 5, 250.0, 6000.0); Liste kjøretøy = ny ArrayList (); kjøretøy.add (sedan); vehicles.add (crossover); String jsonDataString = mapper.writeValueAsString (kjøretøy);

jsonDataString inneholder følgende JSON-array:

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

Til slutt vil vi bevise tilstedeværelsen eller fraværet av forskjellige eiendomsnavn i den resulterende JSON-strengen:

assertThat (jsonDataString, containString ("make")); assertThat (jsonDataString, ikke (inneholderString ("modell"))); assertThat (jsonDataString, ikke (inneholderString ("sitteplasserCapacity"))); assertThat (jsonDataString, ikke (inneholderString ("topSpeed")); assertThat (jsonDataString, containString ("towingCapacity"));

3.2. Mix-ins

Mix-ins tillater oss å bruke oppførsel (for eksempel å ignorere egenskaper når vi serialiserer og deserialiserer) uten at vi trenger å bruke merknader direkte på en klasse. Dette er spesielt nyttig når det gjelder tredjepartsklasser der vi ikke kan endre koden direkte.

Denne underavsnittet gjenbruker klassearvkjeden som ble introdusert i forrige, bortsett fra at @JsonIgnore og @JsonIgnoreProperties merknader om Bil klasse er fjernet:

offentlig abstrakt klasse Bil utvider kjøretøy {private int sitteplasserKapasitet; privat dobbel topphastighet; // felt, konstruktører, getters og settere}

For å demonstrere operasjoner av mix-ins, vil vi ignorere Vehicle.make og Car.topSpeed egenskaper, og bruk deretter en test for å sikre at alt fungerer som forventet.

Det første trinnet er å erklære en innblandingstype:

privat abstrakt klasse CarMixIn {@JsonIgnorer offentlig strengmerke; @JsonIgnorer offentlig streng topSpeed; }

Deretter er innblandingen bundet til en dataklasse gjennom en ObjectMapper gjenstand:

ObjectMapper mapper = ny ObjectMapper (); mapper.addMixIn (Car.class, CarMixIn.class);

Etter det instantierer vi dataobjekter og serierer dem i en streng:

Sedan sedan = ny Sedan ("Mercedes-Benz", "S500", 5, 250.0); Crossover crossover = ny Crossover ("BMW", "X6", 5, 250.0, 6000.0); Liste kjøretøy = ny ArrayList (); kjøretøy.add (sedan); vehicles.add (crossover); String jsonDataString = mapper.writeValueAsString (kjøretøy);

jsonDataString inneholder nå følgende JSON:

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

Til slutt, la oss verifisere resultatet:

assertThat (jsonDataString, ikke (inneholderString ("make"))); assertThat (jsonDataString, inneholderString ("modell")); assertThat (jsonDataString, containString ("sittekapasitet")); assertThat (jsonDataString, ikke (inneholderString ("topSpeed"))); assertThat (jsonDataString, containString ("towingCapacity"));

3.3. Kommentar Introspeksjon

Annotasjon introspeksjon er den kraftigste metoden for å ignorere supertypeegenskaper, siden den gir mulighet for detaljert tilpasning ved hjelp av AnnotationIntrospector.hasIgnoreMarker API.

Denne underdelen bruker det samme klassehierarkiet som det forrige. I dette brukstilfellet vil vi be Jackson om å ignorere Kjøretøy. Modell, Crossover.towingCapacity og alle eiendommer deklarert i Bil klasse. La oss starte med erklæringen om en klasse som utvider Jackson Kommentar Introspektor grensesnitt:

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

Introspektøren vil ignorere noen egenskaper (det vil si at den vil behandle dem som om de ble merket som ignorert via en av de andre metodene) som samsvarer med settet med betingelser definert i metoden.

Neste trinn er å registrere en forekomst av Uvitenhet Introspektor klasse med en ObjectMapper gjenstand:

ObjectMapper mapper = ny ObjectMapper (); mapper.setAnnotationIntrospector (ny IgnoranceIntrospector ());

Nå oppretter og serier vi dataobjekter på samme måte som i avsnitt 3.2. Innholdet i den nylig produserte strengen er:

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

Til slutt vil vi bekrefte at introspektøren fungerte som forutsatt:

assertThat (jsonDataString, containString ("make")); assertThat (jsonDataString, ikke (inneholderString ("modell"))); assertThat (jsonDataString, ikke (inneholderString ("sittekapasitet"))); assertThat (jsonDataString, ikke (inneholderString ("topSpeed"))); assertThat (jsonDataString, ikke (inneholderString ("towingCapacity")));

4. Undertype håndteringsscenarier

Denne delen vil omhandle to interessante scenarier som er relevante for håndtering av underklasser.

4.1. Konvertering mellom undertyper

Jackson lar et objekt konverteres til en annen type enn den opprinnelige. Faktisk kan denne konverteringen skje blant alle kompatible typer, men den er mest nyttig når den brukes mellom to undertyper av samme grensesnitt eller klasse for å sikre verdier og funksjonalitet.

For å demonstrere konvertering av en type til en annen, vil vi gjenbruke Kjøretøy hierarki hentet fra seksjon 2, med tillegg av @JsonIgnore kommentar på eiendommer i Bil og Lastebil for å unngå inkompatibilitet.

offentlig klasse Bil utvider kjøretøyet {@JsonIgnorer privat int sitteplasserKapasitet; @JsonIgnorer privat dobbel topSpeed; // konstruktører, getters og setters} offentlig klasse Truck utvider kjøretøy {@JsonIgnorer privat dobbelt nyttelastCapacity; // konstruktører, getters og setters}

Følgende kode vil bekrefte at en konvertering er vellykket, og at det nye objektet bevarer dataverdiene fra det gamle:

ObjectMapper mapper = ny ObjectMapper (); Bilbil = ny bil ("Mercedes-Benz", "S500", 5, 250.0); Lastebil = mapper.convertValue (bil, lastebil.klasse); assertEquals ("Mercedes-Benz", truck.getMake ()); assertEquals ("S500", truck.getModel ());

4.2. Deserialisering uten konstruktører uten argus

Som standard gjenskaper Jackson dataobjekter ved å bruke konstruktører uten arg. Dette er upraktisk i noen tilfeller, for eksempel når en klasse har ikke-standardkonstruktører og brukere må skrive no-arg-bare for å tilfredsstille Jacksons krav. Det er enda mer plagsomt i et klassehierarki der en ikke-arg-konstruktør må legges til en klasse og alle de som er høyere i arvkjeden. I disse tilfellene, skapermetoder komme til unnsetning.

Denne delen vil bruke en objektstruktur som ligner den i seksjon 2, med noen endringer i konstruktørene. Spesielt faller alle ikke-arg-konstruktører bort, og konstruktører av konkrete undertyper er merket med @JsonCreator og @JsonProperty for å gjøre dem til skapermetoder.

public class Car extends Vehicle {@JsonCreator public Car (@JsonProperty ("make") String make, @JsonProperty ("model") Strengmodell, @JsonProperty ("sittende") int sitteplasserCapacity, @JsonProperty ("topSpeed") dobbel topp ) {super (merke, modell); this.seatingCapacity = sitteplasserCapacity; this.topSpeed ​​= topSpeed; } // felt, getters og setters} offentlig klasse Truck utvider kjøretøy {@JsonCreator public Truck (@JsonProperty ("make") String make, @JsonProperty ("model") Strengmodell, @JsonProperty ("nyttelast") dobbelt nyttelastCapacity) {super (merke, modell); this.payloadCapacity = nyttelastCapacity; } // felt, getters og setters}

En test vil verifisere at Jackson kan håndtere objekter som mangler ingen arg-konstruktører:

ObjectMapper mapper = ny ObjectMapper (); mapper.enableDefaultTyping (); Bilbil = ny bil ("Mercedes-Benz", "S500", 5, 250.0); Lastebil = ny lastebil ("Isuzu", "NQR", 7500.0); Liste kjøretøy = ny ArrayList (); vehicles.add (bil); vehicles.add (lastebil); Fleet serializedFleet = new Fleet (); serializedFleet.setVehicles (kjøretøy); String jsonDataString = mapper.writeValueAsString (serializedFleet); mapper.readValue (jsonDataString, Fleet.class);

5. Konklusjon

Denne opplæringen har dekket flere interessante bruksområder for å demonstrere Jacksons støtte til typearv, med fokus på polymorfisme og uvitenhet om supertypeegenskaper.

Implementeringen av alle disse eksemplene og kodebiter kan bli funnet i et GitHub-prosjekt.