Jackson - bidirektionale Beziehungen
1. Überblick
In diesem Tutorial gehen wir auf die besten Möglichkeiten ein, mitbidirectional relationships in Jackson umzugehen.
Wir werden das Problem der unendlichen Rekursion von Jackson JSON diskutieren und dann - wir werden sehen, wie Entitäten mit bidirektionalen Beziehungen serialisiert werden - und schließlich werden wir sie deserialisieren.
2. Unendliche Rekursion
Schauen wir uns zunächst das Problem der unendlichen Rekursion in Jackson an. Im folgenden Beispiel haben wir zwei Entitäten - "User" und "Item" - mita simple one-to-many relationship:
Die Entität "User":
public class User {
public int id;
public String name;
public List- userItems;
}
Die Entität "Item":
public class Item {
public int id;
public String itemName;
public User owner;
}
Wenn wir versuchen, eine Instanz von „Item“ zu serialisieren, löst Jackson eine Ausnahme vonJsonMappingExceptionaus:
@Test(expected = JsonMappingException.class)
public void givenBidirectionRelation_whenSerializing_thenException()
throws JsonProcessingException {
User user = new User(1, "John");
Item item = new Item(2, "book", user);
user.addItem(item);
new ObjectMapper().writeValueAsString(item);
}
Dasfull exception ist:
com.fasterxml.jackson.databind.JsonMappingException:
Infinite recursion (StackOverflowError)
(through reference chain:
org.example.jackson.bidirection.Item["owner"]
->org.example.jackson.bidirection.User["userItems"]
->java.util.ArrayList[0]
->org.example.jackson.bidirection.Item["owner"]
->…..
Lassen Sie uns im Verlauf der nächsten Abschnitte sehen, wie Sie dieses Problem lösen können.
3. Verwenden Sie@JsonManagedReference,@JsonBackReference
Lassen Sie uns zunächst die Beziehung mit@JsonManagedReference,@JsonBackReference kommentieren, damit Jackson die Beziehung besser handhaben kann:
Hier ist die Entität "User":
public class User {
public int id;
public String name;
@JsonBackReference
public List- userItems;
}
Und die "Item":
public class Item {
public int id;
public String itemName;
@JsonManagedReference
public User owner;
}
Testen wir nun die neuen Entitäten:
@Test
public void
givenBidirectionRelation_whenUsingJacksonReferenceAnnotation_thenCorrect()
throws JsonProcessingException {
User user = new User(1, "John");
Item item = new Item(2, "book", user);
user.addItem(item);
String result = new ObjectMapper().writeValueAsString(item);
assertThat(result, containsString("book"));
assertThat(result, containsString("John"));
assertThat(result, not(containsString("userItems")));
}
Hier ist die Ausgabe der Serialisierung:
{
"id":2,
"itemName":"book",
"owner":
{
"id":1,
"name":"John"
}
}
Beachten Sie, dass:
-
@JsonManagedReference ist der vordere Teil der Referenz - derjenige, der normal serialisiert wird.
-
@JsonBackReference ist der hintere Teil der Referenz - er wird bei der Serialisierung weggelassen.
4. Verwenden Sie@JsonIdentityInfo
Lassen Sie uns nun sehen, wie Sie bei der Serialisierung von Entitäten mit bidirektionaler Beziehung mithilfe von@JsonIdentityInfo helfen können.
Wir fügen die Annotation auf Klassenebene zu unserer Entität "User" hinzu:
@JsonIdentityInfo(
generator = ObjectIdGenerators.PropertyGenerator.class,
property = "id")
public class User { ... }
Und zur Entität „Item“:
@JsonIdentityInfo(
generator = ObjectIdGenerators.PropertyGenerator.class,
property = "id")
public class Item { ... }
Zeit für den Test:
@Test
public void givenBidirectionRelation_whenUsingJsonIdentityInfo_thenCorrect()
throws JsonProcessingException {
User user = new User(1, "John");
Item item = new Item(2, "book", user);
user.addItem(item);
String result = new ObjectMapper().writeValueAsString(item);
assertThat(result, containsString("book"));
assertThat(result, containsString("John"));
assertThat(result, containsString("userItems"));
}
Hier ist die Ausgabe der Serialisierung:
{
"id":2,
"itemName":"book",
"owner":
{
"id":1,
"name":"John",
"userItems":[2]
}
}
5. Verwenden Sie@JsonIgnore
Alternativ können wir auch die Annotation@JsonIgnore zu einfachignore one of the sides of the relationship verwenden, wodurch die Kette unterbrochen wird.
Im folgenden Beispiel verhindern wir die unendliche Rekursion, indem wir die Eigenschaft "User" "userItems" bei der Serialisierung ignorieren:
Hier ist die Entität "User":
public class User {
public int id;
public String name;
@JsonIgnore
public List- userItems;
}
Und hier ist unser Test:
@Test
public void givenBidirectionRelation_whenUsingJsonIgnore_thenCorrect()
throws JsonProcessingException {
User user = new User(1, "John");
Item item = new Item(2, "book", user);
user.addItem(item);
String result = new ObjectMapper().writeValueAsString(item);
assertThat(result, containsString("book"));
assertThat(result, containsString("John"));
assertThat(result, not(containsString("userItems")));
}
Und hier ist die Ausgabe der Serialisierung:
{
"id":2,
"itemName":"book",
"owner":
{
"id":1,
"name":"John"
}
}
6. Verwenden Sie@JsonView
Wir können auch die neuere Annotation@JsonViewverwenden, um eine Seite der Beziehung auszuschließen.
Im folgenden Beispiel verwenden wirtwo JSON Views – Public and Internal, wobeiInternalPublic erweitert:
public class Views {
public static class Public {}
public static class Internal extends Public {}
}
Wir werden alle FelderUser undItem in die AnsichtPublic aufnehmen -except the User field userItems, die in die AnsichtInternal aufgenommen werden:
Hier ist unsere Entität „User“:
public class User {
@JsonView(Views.Public.class)
public int id;
@JsonView(Views.Public.class)
public String name;
@JsonView(Views.Internal.class)
public List- userItems;
}
Und hier ist unsere Entität „Item“:
public class Item {
@JsonView(Views.Public.class)
public int id;
@JsonView(Views.Public.class)
public String itemName;
@JsonView(Views.Public.class)
public User owner;
}
Wenn wir mit der AnsichtPublicerialisieren, funktioniert dies ordnungsgemäß -because we excluded userItems wird nicht serialisiert:
@Test
public void givenBidirectionRelation_whenUsingPublicJsonView_thenCorrect()
throws JsonProcessingException {
User user = new User(1, "John");
Item item = new Item(2, "book", user);
user.addItem(item);
String result = new ObjectMapper().writerWithView(Views.Public.class)
.writeValueAsString(item);
assertThat(result, containsString("book"));
assertThat(result, containsString("John"));
assertThat(result, not(containsString("userItems")));
}
Wenn wir jedoch mit der AnsichtInternal serialisieren, wirdJsonMappingException ausgelöst, da alle Felder enthalten sind:
@Test(expected = JsonMappingException.class)
public void givenBidirectionRelation_whenUsingInternalJsonView_thenException()
throws JsonProcessingException {
User user = new User(1, "John");
Item item = new Item(2, "book", user);
user.addItem(item);
new ObjectMapper()
.writerWithView(Views.Internal.class)
.writeValueAsString(item);
}
7. Verwenden Sie einen benutzerdefinierten Serializer
Weiter - Lassen Sie uns sehen, wie Entitäten mit bidirektionaler Beziehung mithilfe eines benutzerdefinierten Serializers serialisiert werden.
Im folgenden Beispiel verwenden wir einen benutzerdefinierten Serializer, um die Eigenschaft "User" "userItems" zu serialisieren:
Hier ist die Entität "User":
public class User {
public int id;
public String name;
@JsonSerialize(using = CustomListSerializer.class)
public List- userItems;
}
Und hier ist das „CustomListSerializer“:
public class CustomListSerializer extends StdSerializer>{
public CustomListSerializer() {
this(null);
}
public CustomListSerializer(Class t) {
super(t);
}
@Override
public void serialize(
List- items,
JsonGenerator generator,
SerializerProvider provider)
throws IOException, JsonProcessingException {
List
ids = new ArrayList<>();
for (Item item : items) {
ids.add(item.id);
}
generator.writeObject(ids);
}
}
Testen wir nun den Serializer und sehen, welche Art von Ausgabe erzeugt wird:
@Test
public void givenBidirectionRelation_whenUsingCustomSerializer_thenCorrect()
throws JsonProcessingException {
User user = new User(1, "John");
Item item = new Item(2, "book", user);
user.addItem(item);
String result = new ObjectMapper().writeValueAsString(item);
assertThat(result, containsString("book"));
assertThat(result, containsString("John"));
assertThat(result, containsString("userItems"));
}
Undthe final output der Serialisierung mit dem benutzerdefinierten Serializer:
{
"id":2,
"itemName":"book",
"owner":
{
"id":1,
"name":"John",
"userItems":[2]
}
}
8. Deserialisieren Sie mit@JsonIdentityInfo
Nun wollen wir sehen, wie Entitäten mit bidirektionaler Beziehung mit@JsonIdentityInfo deserialisiert werden.
Hier ist die Entität "User":
@JsonIdentityInfo(
generator = ObjectIdGenerators.PropertyGenerator.class,
property = "id")
public class User { ... }
Und die Entität "Item":
@JsonIdentityInfo(
generator = ObjectIdGenerators.PropertyGenerator.class,
property = "id")
public class Item { ... }
Schreiben wir jetzt einen kurzen Test - beginnend mit einigen manuellen JSON-Daten, die wir analysieren möchten, und endend mit der korrekt konstruierten Entität:
@Test
public void givenBidirectionRelation_whenDeserializingWithIdentity_thenCorrect()
throws JsonProcessingException, IOException {
String json =
"{\"id\":2,\"itemName\":\"book\",\"owner\":{\"id\":1,\"name\":\"John\",\"userItems\":[2]}}";
ItemWithIdentity item
= new ObjectMapper().readerFor(ItemWithIdentity.class).readValue(json);
assertEquals(2, item.id);
assertEquals("book", item.itemName);
assertEquals("John", item.owner.name);
}
9. Verwenden Sie Custom Deserializer
Lassen Sie uns abschließend die Entitäten mit bidirektionaler Beziehung mithilfe eines benutzerdefinierten Deserialisierers deserialisieren.
Im folgenden Beispiel verwenden wir den benutzerdefinierten Deserializer, um die Eigenschaft "User" "userItems" zu analysieren:
Hier ist die Entität "User":
public class User {
public int id;
public String name;
@JsonDeserialize(using = CustomListDeserializer.class)
public List- userItems;
}
Und hier ist unser „CustomListDeserializer“:
public class CustomListDeserializer extends StdDeserializer>{
public CustomListDeserializer() {
this(null);
}
public CustomListDeserializer(Class> vc) {
super(vc);
}
@Override
public List- deserialize(
JsonParser jsonparser,
DeserializationContext context)
throws IOException, JsonProcessingException {
return new ArrayList<>();
}
}
Und der einfache Test:
@Test
public void givenBidirectionRelation_whenUsingCustomDeserializer_thenCorrect()
throws JsonProcessingException, IOException {
String json =
"{\"id\":2,\"itemName\":\"book\",\"owner\":{\"id\":1,\"name\":\"John\",\"userItems\":[2]}}";
Item item = new ObjectMapper().readerFor(Item.class).readValue(json);
assertEquals(2, item.id);
assertEquals("book", item.itemName);
assertEquals("John", item.owner.name);
}
10. Fazit
In diesem Lernprogramm wurde veranschaulicht, wie Entitäten mit bidirektionalen Beziehungen mithilfe von Jackson serialisiert / deserialisiert werden.
Die Implementierung all dieser Beispiele und Codefragmentecan be found in our GitHub project - dies ist ein Maven-basiertes Projekt, daher sollte es einfach zu importieren und auszuführen sein, wie es ist.