Jackson - bidirektionale Beziehungen

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.