Jackson - Bidirectionele relaties

1. Overzicht

In deze tutorial bespreken we de beste manieren om ermee om te gaan bidirectionele relaties in Jackson.

We zullen het Jackson JSON oneindige recursieprobleem bespreken, dan - we zullen zien hoe we entiteiten kunnen serialiseren met bidirectionele relaties en tot slot - we zullen ze deserialiseren.

2. Oneindige recursie

Laten we eerst eens kijken naar het probleem van de oneindige recursie van Jackson. In het volgende voorbeeld hebben we twee entiteiten: "Gebruiker"En"Item”- met een eenvoudige een-op-veel-relatie:

De "Gebruiker" entiteit:

openbare klasse Gebruiker {openbare int id; public String naam; openbare lijst userItems; }

De "Item" entiteit:

public class Item {public int id; openbare String itemName; openbare gebruiker eigenaar; }

Wanneer we een instantie van 'Item“, Jackson zal een JsonMappingException uitzondering:

@Test (verwacht = JsonMappingException.class) public void givenBidirectionRelation_whenSerializing_thenException () gooit JsonProcessingException {User user = nieuwe gebruiker (1, "John"); Item item = nieuw item (2, "boek", gebruiker); user.addItem (item); nieuwe ObjectMapper (). writeValueAsString (item); }

De volledige uitzondering is:

com.fasterxml.jackson.databind.JsonMappingException: Oneindige recursie (StackOverflowError) (via referentieketen: org.baeldung.jackson.bidirection.Item ["owner"] -> org.baeldung.jackson.bidirection.User ["userItems"] -> java.util.ArrayList [0] -> org.baeldung.jackson.bidirection.Item ["eigenaar"] ->… ..

Laten we in de loop van de volgende secties kijken hoe we dit probleem kunnen oplossen.

3. Gebruik @JsonManagedReference, @JsonBackReference

Laten we eerst de relatie met annoteren @JsonManagedReference, @JsonBackReference om Jackson in staat te stellen de relatie beter te behandelen:

Hier is de "Gebruiker" entiteit:

openbare klasse Gebruiker {openbare int id; public String naam; @JsonBackReference openbare lijst userItems; }

En de "Item“:

public class Item {public int id; openbare String itemName; @JsonManagedReference openbare gebruiker eigenaar; }

Laten we nu de nieuwe entiteiten testen:

@Test openbare ongeldige gegevenBidirectionRelation_whenUsingJacksonReferenceAnnotation_thenCorrect () gooit JsonProcessingException {User user = new User (1, "John"); Item item = nieuw item (2, "boek", gebruiker); user.addItem (item); String resultaat = nieuwe ObjectMapper (). WriteValueAsString (item); assertThat (resultaat, bevatString ("boek")); assertThat (resultaat, bevatString ("John")); assertThat (resultaat, niet (bevatString ("userItems"))); }

Hier is de output van serialisatie:

{"id": 2, "itemName": "book", "owner": {"id": 1, "name": "John"}}

Let daar op:

  • @JsonManagedReference is het voorste deel van de referentie - degene die normaal wordt geserialiseerd.
  • @JsonBackReference is het achterste deel van de referentie - het wordt weggelaten uit de serialisatie.

4. Gebruik @JsonIdentityInfo

Laten we nu eens kijken hoe we kunnen helpen met de serialisatie van entiteiten met een bidirectionele relatie met behulp van @JsonIdentityInfo.

We voegen de annotatie op klasniveau toe aan onze "Gebruiker" entiteit:

@JsonIdentityInfo (generator = ObjectIdGenerators.PropertyGenerator.class, property = "id") openbare klasse Gebruiker {...}

En naar de "Item" entiteit:

@JsonIdentityInfo (generator = ObjectIdGenerators.PropertyGenerator.class, property = "id") openbare klasse Item {...}

Tijd voor de test:

@Test openbare ongeldige gegevenBidirectionRelation_whenUsingJsonIdentityInfo_thenCorrect () genereert JsonProcessingException {User user = new User (1, "John"); Item item = nieuw item (2, "boek", gebruiker); user.addItem (item); String resultaat = nieuwe ObjectMapper (). WriteValueAsString (item); assertThat (resultaat, bevatString ("boek")); assertThat (resultaat, bevatString ("John")); assertThat (resultaat, bevatString ("userItems")); }

Hier is de output van serialisatie:

{"id": 2, "itemName": "book", "owner": {"id": 1, "name": "John", "userItems": [2]}}

5. Gebruik @JsonIgnore

Als alternatief kunnen we ook de @JsonIgnore annotatie om eenvoudig negeer een van de kanten van de relatie, waardoor de ketting wordt verbroken.

In het volgende voorbeeld - zullen we de oneindige recursie voorkomen door de "Gebruiker" eigendom "userItems"Van serialisatie:

Hier is "Gebruiker" entiteit:

openbare klasse Gebruiker {openbare int id; public String naam; @JsonIgnore openbare lijst userItems; }

En hier is onze test:

@Test openbare ongeldige gegevenBidirectionRelation_whenUsingJsonIgnore_thenCorrect () gooit JsonProcessingException {User user = new User (1, "John"); Item item = nieuw item (2, "boek", gebruiker); user.addItem (item); String resultaat = nieuwe ObjectMapper (). WriteValueAsString (item); assertThat (resultaat, bevatString ("boek")); assertThat (resultaat, bevatString ("John")); assertThat (resultaat, niet (bevatString ("userItems"))); }

En hier is de output van serialisatie:

{"id": 2, "itemName": "book", "owner": {"id": 1, "name": "John"}}

6. Gebruik @JsonView

We kunnen ook de nieuwere gebruiken @JsonView annotatie om één kant van de relatie uit te sluiten.

In het volgende voorbeeld gebruiken we twee JSON-weergaven - Openbaar en Intern waar Intern strekt zich uit Openbaar:

public class Weergaven {public static class Public {} public static class Intern breidt openbaar uit {}}

We zullen alles opnemen Gebruiker en Item velden in de Openbaar Visie - behalve de Gebruiker veld- userItems die zal worden opgenomen in de Intern Visie:

Hier is onze entiteit "Gebruiker“:

openbare klasse Gebruiker {@JsonView (Views.Public.class) openbare int id; @JsonView (Views.Public.class) public String naam; @JsonView (Views.Internal.class) openbare lijst userItems; }

En hier is onze entiteit "Item“:

public class Item {@JsonView (Views.Public.class) public int id; @JsonView (Views.Public.class) public String itemName; @JsonView (Views.Public.class) openbare gebruiker eigenaar; }

Wanneer we serialiseren met behulp van de Openbaar bekijken, het werkt correct - omdat we uitgesloten zijn userItems van worden geserialiseerd:

@Test openbare ongeldige gegevenBidirectionRelation_whenUsingPublicJsonView_thenCorrect () gooit JsonProcessingException {User user = new User (1, "John"); Item item = nieuw item (2, "boek", gebruiker); user.addItem (item); String resultaat = nieuwe ObjectMapper (). WriterWithView (Views.Public.class) .writeValueAsString (item); assertThat (resultaat, bevatString ("boek")); assertThat (resultaat, bevatString ("John")); assertThat (resultaat, niet (bevatString ("userItems"))); }

Maar als we serialiseren met behulp van een Intern visie, JsonMappingException wordt gegenereerd omdat alle velden zijn opgenomen:

@Test (verwacht = JsonMappingException.class) openbare ongeldige gegevenBidirectionRelation_whenUsingInternalJsonView_thenException () gooit JsonProcessingException {User user = new User (1, "John"); Item item = nieuw item (2, "boek", gebruiker); user.addItem (item); nieuwe ObjectMapper () .writerWithView (Views.Internal.class) .writeValueAsString (item); }

7. Gebruik een aangepaste serialisator

Laten we nu eens kijken hoe we entiteiten met een bidirectionele relatie kunnen serialiseren met behulp van een aangepaste serialisator.

In het volgende voorbeeld zullen we een aangepaste serialisator gebruiken om de "Gebruiker" eigendom "userItems“:

Hier is de "Gebruiker" entiteit:

openbare klasse Gebruiker {openbare int id; public String naam; @JsonSerialize (using = CustomListSerializer.class) openbare lijst userItems; }

En hier is de "CustomListSerializer“:

openbare klasse CustomListSerializer breidt StdSerializer uit{openbare CustomListSerializer () {dit (null); } openbare CustomListSerializer (Klasse t) {super (t); } @Override public void serialize (Lijstitems, JsonGenerator-generator, SerializerProvider-provider) gooit IOException, JsonProcessingException {List ids = new ArrayList (); voor (Item item: items) {ids.add (item.id); } generator.writeObject (ids); }}

Laten we nu de serialisator testen en kijken wat de juiste soort uitvoer wordt geproduceerd:

@Test openbare ongeldige gegevenBidirectionRelation_whenUsingCustomSerializer_thenCorrect () gooit JsonProcessingException {User user = new User (1, "John"); Item item = nieuw item (2, "boek", gebruiker); user.addItem (item); String resultaat = nieuwe ObjectMapper (). WriteValueAsString (item); assertThat (resultaat, bevatString ("boek")); assertThat (resultaat, bevatString ("John")); assertThat (resultaat, bevatString ("userItems")); }

En de uiteindelijke output van de serialisering met de aangepaste serialisator:

{"id": 2, "itemName": "book", "owner": {"id": 1, "name": "John", "userItems": [2]}}

8. Deserialiseren met @JsonIdentityInfo

Laten we nu eens kijken hoe we entiteiten met een bidirectionele relatie kunnen deserialiseren met behulp van @JsonIdentityInfo.

Hier is de "Gebruiker" entiteit:

@JsonIdentityInfo (generator = ObjectIdGenerators.PropertyGenerator.class, property = "id") openbare klasse Gebruiker {...}

En de "Item" entiteit:

@JsonIdentityInfo (generator = ObjectIdGenerators.PropertyGenerator.class, property = "id") openbare klasse Item {...}

Laten we nu een snelle test schrijven - beginnend met enkele handmatige JSON-gegevens die we willen ontleden en eindigen met de correct geconstrueerde entiteit:

@Test openbare ongeldige gegevenBidirectionRelation_whenDeserializingWithIdentity_thenCorrect () genereert JsonProcessingException, IOException {String json = "{\" id \ ": 2, \" itemName \ ": \" boek \ ", \" eigenaar \ ": {\" id \ ": 1, \ "naam \": \ "John \", \ "userItems \": [2]}} "; ItemWithIdentity item = nieuwe ObjectMapper (). ReaderFor (ItemWithIdentity.class) .readValue (json); assertEquals (2, item.id); assertEquals ("boek", item.itemName); assertEquals ("John", item.owner.name); }

9. Gebruik Custom Deserializer

Laten we tot slot de entiteiten met een bidirectionele relatie deserialiseren met behulp van een aangepaste deserializer.

In het volgende voorbeeld zullen we een aangepaste deserializer gebruiken om de "Gebruiker" eigendom "userItems“:

Hier is "Gebruiker" entiteit:

openbare klasse Gebruiker {openbare int id; public String naam; @JsonDeserialize (using = CustomListDeserializer.class) openbare lijst userItems; }

En hier is onze "CustomListDeserializer“:

openbare klasse CustomListDeserializer breidt StdDeserializer uit{openbare CustomListDeserializer () {dit (null); } openbare CustomListDeserializer (Klasse vc) {super (vc); } @Override public List deserialize (JsonParser jsonparser, DeserializationContext context) gooit IOException, JsonProcessingException {return new ArrayList (); }}

En de simpele test:

@Test openbare ongeldig gegevenBidirectionRelation_whenUsingCustomDeserializer_thenCorrect () gooit JsonProcessingException, IOException {String json = "{\" id \ ": 2, \" itemName \ ": \" boek \ ", \" eigenaar \ ": {\" id \ ": 1, \ "naam \": \ "John \", \ "userItems \": [2]}} "; Item item = nieuwe ObjectMapper (). ReaderFor (Item.class) .readValue (json); assertEquals (2, item.id); assertEquals ("boek", item.itemName); assertEquals ("John", item.owner.name); }

10. Conclusie

In deze zelfstudie hebben we geïllustreerd hoe u entiteiten met bidirectionele relaties kunt serialiseren / deserialiseren met Jackson.

De implementatie van al deze voorbeelden en codefragmenten is te vinden in ons GitHub-project - dit is een op Maven gebaseerd project, dus het zou gemakkelijk moeten kunnen worden geïmporteerd en uitgevoerd zoals het is.