Jackson - Relazioni bidirezionali

1. Panoramica

In questo tutorial, esamineremo i modi migliori per gestire le relazioni bidirezionali in Jackson .

Discuteremo il problema della ricorsione infinita Jackson JSON, poi - vedremo come serializzare entità con relazioni bidirezionali e infine - le deserializzeremo.

2. Ricorsione infinita

Primo: diamo un'occhiata al problema della ricorsione infinita di Jackson. Nell'esempio seguente abbiamo due entità - " Utente " e " Articolo " - con una semplice relazione uno-a-molti :

L' entità " Utente ":

public class User { public int id; public String name; public List userItems; }

L' entità " Articolo ":

public class Item { public int id; public String itemName; public User owner; }

Quando proviamo a serializzare un'istanza di " Item ", Jackson lancia un'eccezione JsonMappingException :

@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); }

L' eccezione completa è:

com.fasterxml.jackson.databind.JsonMappingException: Infinite recursion (StackOverflowError) (through reference chain: org.baeldung.jackson.bidirection.Item["owner"] ->org.baeldung.jackson.bidirection.User["userItems"] ->java.util.ArrayList[0] ->org.baeldung.jackson.bidirection.Item["owner"] ->…..

Vediamo, nel corso delle prossime sezioni, come risolvere questo problema.

3. Utilizzare @JsonManagedReference , @JsonBackReference

Innanzitutto, annotiamo la relazione con @JsonManagedReference , @JsonBackReference per consentire a Jackson di gestire meglio la relazione:

Ecco l' entità " Utente ":

public class User { public int id; public String name; @JsonBackReference public List userItems; }

E l '" Articolo ":

public class Item { public int id; public String itemName; @JsonManagedReference public User owner; }

Proviamo ora le nuove entità:

@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"))); }

Ecco l'output della serializzazione:

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

Nota che:

  • @JsonManagedReference è la parte di riferimento in avanti, quella che viene serializzata normalmente.
  • @JsonBackReference è la parte posteriore del riferimento: verrà omesso dalla serializzazione.

4. Utilizza @JsonIdentityInfo

Vediamo ora come aiutare con la serializzazione di entità con relazione bidirezionale utilizzando @JsonIdentityInfo .

Aggiungiamo l'annotazione a livello di classe alla nostra entità " Utente ":

@JsonIdentityInfo( generator = ObjectIdGenerators.PropertyGenerator.class, property = "id") public class User { ... }

E all'entità " Articolo ":

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

Tempo per il 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")); }

Ecco l'output della serializzazione:

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

5. Usa @JsonIgnore

In alternativa, possiamo anche utilizzare l' annotazione @JsonIgnore per ignorare semplicemente uno dei lati della relazione , interrompendo così la catena.

Nell'esempio seguente, impediremo la ricorsione infinita ignorando la proprietà " User " " userItems " dalla serializzazione:

Ecco l' entità " Utente ":

public class User { public int id; public String name; @JsonIgnore public List userItems; }

Ed ecco il nostro 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"))); }

Ed ecco l'output della serializzazione:

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

6. Usa @JsonView

Possiamo anche utilizzare la più recente annotazione @JsonView per escludere un lato della relazione.

Nell'esempio seguente, utilizziamo due viste JSON: pubblica e interna dove Interno estende il pubblico :

public class Views { public static class Public {} public static class Internal extends Public {} }

Includeremo tutti i campi Utente e Elemento nella visualizzazione pubblica , ad eccezione del campo Utente userItems che sarà incluso nella visualizzazione interna :

Ecco la nostra entità " Utente ":

public class User { @JsonView(Views.Public.class) public int id; @JsonView(Views.Public.class) public String name; @JsonView(Views.Internal.class) public List userItems; }

Ed ecco la nostra entità " 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; }

Quando serializziamo utilizzando la visualizzazione pubblica , funziona correttamente, perché abbiamo escluso userItems dalla serializzazione:

@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"))); }

But If we serialize using an Internal view, JsonMappingException is thrown because all the fields are included:

@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. Use a Custom Serializer

Next – let's see how to serialize entities with bidirectional relationship using a custom serializer.

In the following example – we will use a custom serializer to serialize the “User” property “userItems“:

Here's the “User” entity:

public class User { public int id; public String name; @JsonSerialize(using = CustomListSerializer.class) public List userItems; }

And here is the “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); } }
    

Let's now test out the serializer and see the right kind of output being produced:

@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")); }

And the final output of the serialization with the custom serializer:

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

8. Deserialize With @JsonIdentityInfo

Now – let's see how to deserialize entities with bidirectional relationship using @JsonIdentityInfo.

Here is the “User” entity:

@JsonIdentityInfo( generator = ObjectIdGenerators.PropertyGenerator.class, property = "id") public class User { ... }

And the “Item” entity:

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

Let's now write a quick test – starting with some manual JSON data we want to parse and finishing with the correctly constructed entity:

@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. Use Custom Deserializer

Finally, let's deserialize the entities with bidirectional relationship using a custom deserializer.

In the following example – we will use custom deserializer to parse the “User” property “userItems“:

Here's “User” entity:

public class User { public int id; public String name; @JsonDeserialize(using = CustomListDeserializer.class) public List userItems; }

And here is our “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(); } }
    

And the simple 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. Conclusion

In questo tutorial, abbiamo illustrato come serializzare / deserializzare entità con relazioni bidirezionali utilizzando Jackson.

L'implementazione di tutti questi esempi e frammenti di codice può essere trovata nel nostro progetto GitHub : questo è un progetto basato su Maven, quindi dovrebbe essere facile da importare ed eseguire così com'è.