Jackson - Dvosmjerni odnosi
1. Pregled
U ovom ćemo uputstvu razmotriti najbolje načine rješavanja dvosmjerni odnosi u Jacksonu.
Razmotrit ćemo problem beskonačne rekurzije Jacksona JSON-a, zatim - vidjet ćemo kako serializirati entitete s dvosmjernim odnosima i na kraju - deserializirati ih.
2. Beskonačna rekurzija
Prvo - pogledajmo Jacksonov problem beskonačne rekurzije. U sljedećem primjeru imamo dva entiteta - “Korisnik"I"Artikal”- sa jednostavan odnos jedan prema mnogima:
"Korisnik”Entitet:
javni razred korisnika {javni int id; naziv javnog niza; javni popis userItems; }
"Artikal”Entitet:
predmet javne klase {public int id; public String itemName; javni korisnik; }
Kada pokušamo serializirati instancu „Artikal“, Jackson će baciti JsonMappingException iznimka:
@Test (očekuje se = JsonMappingException.class) javna praznina givenBidirectionRelation_whenSerializing_thenException () baca JsonProcessingException {Korisnik = novi korisnik (1, "John"); Stavka predmeta = nova stavka (2, "knjiga", korisnik); user.addItem (stavka); novi ObjectMapper (). writeValueAsString (stavka); }
The puna iznimka je:
com.fasterxml.jackson.databind.JsonMappingException: Beskonačna rekurzija (StackOverflowError) (kroz referentni lanac: org.baeldung.jackson.bidirection.Item ["owner"] -> org.baeldung.jackson.bidirection.User ["userItems"] -> java.util.ArrayList [0] -> org.baeldung.jackson.bidirection.Item ["vlasnik"] -> ... ..
Da vidimo, tijekom sljedećih nekoliko odjeljaka - kako riješiti ovaj problem.
3. Koristite @JsonManagedReference, @JsonBackReference
Prvo, zabilježimo odnos s @JsonManagedReference, @JsonBackReference kako bi Jacksonu omogućio da bolje riješi odnos:
Evo "Korisnik”Entitet:
javni razred korisnika {javni int id; naziv javnog niza; @JsonBackReference javni popis userItems; }
I „Artikal“:
predmet javne klase {public int id; public String itemName; @JsonManagedReference javni korisnik; }
Isprobajmo sada nove entitete:
@Test javna praznina givenBidirectionRelation_whenUsingJacksonReferenceAnnotation_thenCorrect () baca JsonProcessingException {User user = novi korisnik (1, "John"); Stavka predmeta = nova stavka (2, "knjiga", korisnik); user.addItem (stavka); Rezultat niza = novi ObjectMapper (). WriteValueAsString (stavka); assertThat (rezultat, sadržiString ("knjiga")); assertThat (rezultat, sadržiString ("Ivan")); assertThat (rezultat, ne (sadržiString ("userItems"))); }
Evo rezultata serializacije:
{"id": 2, "itemName": "book", "owner": {"id": 1, "name": "John"}}
Imajte na umu da:
- @JsonManagedReference je prednji dio reference - onaj koji se normalno serializira.
- @JsonBackReference zadnji je dio reference - izostavit će se iz serializacije.
4. Koristite @JsonIdentityInfo
Sada - pogledajmo kako pomoći u serializaciji entiteta s dvosmjernim odnosom @JsonIdentityInfo.
Dodajemo napomenu na razini predavanja u naš “Korisnik”Entitet:
@JsonIdentityInfo (generator = ObjectIdGenerators.PropertyGenerator.class, property = "id") Korisnik javne klase {...}
I „Artikal”Entitet:
@JsonIdentityInfo (generator = ObjectIdGenerators.PropertyGenerator.class, property = "id") Predmet javne klase {...}
Vrijeme za test:
@Test javna praznina givenBidirectionRelation_whenUsingJsonIdentityInfo_thenCorrect () baca JsonProcessingException {Korisnik = novi korisnik (1, "John"); Stavka predmeta = nova stavka (2, "knjiga", korisnik); user.addItem (stavka); Rezultat niza = novi ObjectMapper (). WriteValueAsString (stavka); assertThat (rezultat, sadržiString ("knjiga")); assertThat (rezultat, sadržiString ("Ivan")); assertThat (rezultat, sadržiString ("userItems")); }
Evo rezultata serializacije:
{"id": 2, "itemName": "book", "owner": {"id": 1, "name": "John", "userItems": [2]}}
5. Koristite @JsonIgnore
Alternativno, možemo koristiti i @JsonIgnore anotacija na jednostavno zanemariti jednu od strana odnosa, prekidajući tako lanac.
U sljedećem primjeru - spriječit ćemo beskonačnu rekurziju ignoriranjem "Korisnik"Svojstvo"Artikli korisnika”Iz serializacije:
Ovdje je "Korisnik”Entitet:
javni razred korisnika {javni int id; naziv javnog niza; @JsonIgnore javni popis userItems; }
I evo našeg testa:
@Test javna praznina givenBidirectionRelation_whenUsingJsonIgnore_thenCorrect () baca JsonProcessingException {Korisnik = novi korisnik (1, "John"); Stavka predmeta = nova stavka (2, "knjiga", korisnik); user.addItem (stavka); Rezultat niza = novi ObjectMapper (). WriteValueAsString (stavka); assertThat (rezultat, sadržiString ("knjiga")); assertThat (rezultat, sadržiString ("Ivan")); assertThat (rezultat, ne (sadržiString ("userItems"))); }
I evo rezultata serializacije:
{"id": 2, "itemName": "book", "owner": {"id": 1, "name": "John"}}
6. Koristite @JsonView
Možemo koristiti i noviju @JsonView napomena za isključivanje jedne strane odnosa.
U sljedećem primjeru - koristimo dva JSON prikaza - Javnost i Interno gdje Interno proteže se Javnost:
prikazi javne klase {public static class Public {} public static class Internal extends Public {}}
Uključit ćemo sve Korisnik i Artikal polja u Javnost Pogled - osim Korisnik polje Artikli korisnika koji će biti uključeni u Interno Pogled:
Evo našeg entiteta “Korisnik“:
javni razred Korisnik {@JsonView (Views.Public.class) javni int id; @JsonView (Views.Public.class) naziv javnog niza; @JsonView (Views.Internal.class) javni popis userItems; }
I ovdje je naš entitet “Artikal“:
stavka javne klase {@JsonView (Views.Public.class) public int id; @JsonView (Views.Public.class) javni String itemName; @JsonView (Views.Public.class) javni korisnik; }
Kad se serializiramo pomoću Javnost pogled, radi ispravno - jer smo isključili Artikli korisnika od serializacije:
@Test javna praznina givenBidirectionRelation_whenUsingPublicJsonView_thenCorrect () baca JsonProcessingException {Korisnik = novi korisnik (1, "John"); Stavka predmeta = nova stavka (2, "knjiga", korisnik); user.addItem (stavka); Rezultat niza = novi ObjectMapper (). WriterWithView (Views.Public.class) .writeValueAsString (stavka); assertThat (rezultat, sadržiString ("knjiga")); assertThat (rezultat, sadržiString ("Ivan")); assertThat (rezultat, ne (sadržiString ("userItems"))); }
Ali ako serializiramo pomoću Interno pogled, JsonMappingException bačen je jer su sva polja uključena:
@Test (očekuje se = JsonMappingException.class) javna praznina givenBidirectionRelation_whenUsingInternalJsonView_thenException () baca JsonProcessingException {Korisnik = novi korisnik (1, "John"); Stavka predmeta = nova stavka (2, "knjiga", korisnik); user.addItem (stavka); novi ObjectMapper () .writerWithView (Views.Internal.class) .writeValueAsString (stavka); }
7. Upotrijebite prilagođeni serilizator
Dalje - pogledajmo kako serializirati entitete s dvosmjernim odnosom pomoću prilagođenog serializatora.
U sljedećem primjeru - koristit ćemo prilagođeni serializator za serializaciju "Korisnik"Svojstvo"Artikli korisnika“:
Evo "Korisnik”Entitet:
javni razred korisnika {javni int id; naziv javnog niza; @JsonSerialize (koristeći = CustomListSerializer.class) javni popis userItems; }
A ovdje je „CustomListSerializer“:
javna klasa CustomListSerializer proširuje StdSerializer{public CustomListSerializer () {this (null); } javni CustomListSerializer (klasa t) {super (t); } @Override public void serialize (Stavke popisa, generator JsonGenerator, dobavljač SerializerProvider) baca IOException, JsonProcessingException {Id popisa = novi ArrayList (); za (Stavka artikla: stavke) {ids.add (item.id); } generator.writeObject (ids); }}
Ajmo sada testirati serializator i vidjeti pravu vrstu rezultata koji se proizvode:
@Test javna praznina givenBidirectionRelation_whenUsingCustomSerializer_thenCorrect () baca JsonProcessingException {Korisnik = novi korisnik (1, "John"); Stavka predmeta = nova stavka (2, "knjiga", korisnik); user.addItem (stavka); Rezultat niza = novi ObjectMapper (). WriteValueAsString (stavka); assertThat (rezultat, sadržiString ("knjiga")); assertThat (rezultat, sadržiString ("Ivan")); assertThat (rezultat, sadržiString ("userItems")); }
I konačni izlaz serializacije s prilagođenim serializatorom:
{"id": 2, "itemName": "book", "owner": {"id": 1, "name": "John", "userItems": [2]}}
8. Deserijalizirajte s @JsonIdentityInfo
Sada - pogledajmo kako deserijalizirati entitete s dvosmjernim odnosom pomoću @JsonIdentityInfo.
Ovdje je "Korisnik”Entitet:
@JsonIdentityInfo (generator = ObjectIdGenerators.PropertyGenerator.class, property = "id") Korisnik javne klase {...}
I „Artikal”Entitet:
@JsonIdentityInfo (generator = ObjectIdGenerators.PropertyGenerator.class, property = "id") Predmet javne klase {...}
Napišimo sada brzi test - počevši od nekih ručnih JSON podataka koje želimo raščlaniti i završavajući ispravno konstruiranim entitetom:
@Test javna praznina givenBidirectionRelation_whenDeserializingWithIdentity_thenCorrect () baca JsonProcessingException, IOException {String json = "{\" id \ ": 2, \" itemName \ ": \" book \ ", \" owner \ ": {\" id \ "id \" 1, \ "name \": \ "John \", \ "userItems \": [2]}} "; ItemWithIdentity item = new ObjectMapper (). ReaderFor (ItemWithIdentity.class) .readValue (json); assertEquals (2, item.id); assertEquals ("knjiga", item.itemName); assertEquals ("John", item.owner.name); }
9. Koristite prilagođeni deserijalizator
Na kraju, hajde deserializirajte entitete s dvosmjernim odnosom koristeći prilagođeni deserijalizator.
U sljedećem primjeru - koristit ćemo prilagođeni deserijalizator za raščlanjivanje "Korisnik"Svojstvo"Artikli korisnika“:
Evo “Korisnik”Entitet:
javni razred korisnika {javni int id; naziv javnog niza; @JsonDeserialize (koristeći = CustomListDeserializer.class) javni popis userItems; }
I evo našeg “CustomListDeserializer“:
javna klasa CustomListDeserializer proširuje StdDeserializer{public CustomListDeserializer () {this (null); } javni CustomListDeserializer (klasa vc) {super (vc); } @Override javni popis deserializira (JsonParser jsonparser, DeserializationContext context) baca IOException, JsonProcessingException {return new ArrayList (); }}
I jednostavan test:
@Test javna praznina givenBidirectionRelation_whenUsingCustomDeserializer_thenCorrect () baca JsonProcessingException, IOException {String json = "{\" id \ ": 2, \" itemName \ ": \" book \ ", \" owner \ ": {\" id \ ": 1, \ "name \": \ "John \", \ "userItems \": [2]}} "; Stavka stavke = novi ObjectMapper (). ReaderFor (Item.class) .readValue (json); assertEquals (2, item.id); assertEquals ("knjiga", item.itemName); assertEquals ("John", item.owner.name); }
10. Zaključak
U ovom uputstvu ilustrirali smo kako serializirati / deserializirati entitete s dvosmjernim odnosima pomoću Jacksona.
Implementacija svih ovih primjera i isječaka koda možete pronaći u našem GitHub projektu - ovo je projekt zasnovan na Mavenu, pa bi ga trebalo biti lako uvesti i pokrenuti kakav jest.