1. Overview
In this tutorial we’ll go over the best ways to deal with bidirectional relationships in Jackson.
We’ll discuss the Jackson JSON infinite recursion problem, then – we’ll see how to serialize entities with bidirectional relationships and finally – we will deserialize them.
2. Jackson Infinite Recursion
First – let’s take a look at the Jackson infinite recursion problem. In the following example we have two entities – “User” and “Item” – with a simple one-to-many relationship:
The “User” entity:
public class User { public int id; public String name; public List<Item> userItems; }
The “Item” entity:
public class Item { public int id; public String itemName; public User owner; }
When we try to serialize an instance of “Item“, Jackson will throw a JsonMappingException exception:
@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); }
The full exception is:
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"] ->…..
Let’s see, over the course of the next few sections – how to solve this problem.
3. Use @JsonManagedReference, @JsonBackReference
First, let’s annotate the relationship with @JsonManagedReference, @JsonBackReference to allow Jackson to better handle the relation:
Here’s the “User” entity:
public class User { public int id; public String name; @JsonBackReference public List<Item> userItems; }
And the “Item“:
public class Item { public int id; public String itemName; @JsonManagedReference public User owner; }
Let’s now test out the new entities:
@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"))); }
Here is the output of serialization:
{ "id":2, "itemName":"book", "owner": { "id":1, "name":"John" } }
Note that:
- @JsonManagedReference is the forward part of reference – the one that gets serialized normally.
- @JsonBackReference is the back part of reference – it will be omitted from serialization.
4. Use @JsonIdentityInfo
Now – let’s see how to help with the serialization of entities with bidirectional relationship using @JsonIdentityInfo.
We add the class level annotation to our “User” entity:
@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id") public class User { ... }
And to the “Item” entity:
@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id") public class Item { ... }
Time for the 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")); }
Here is the output of serialization:
{ "id":2, "itemName":"book", "owner": { "id":1, "name":"John", "userItems":[2] } }
5. Use @JsonIgnore
Alternatively, we can also use the @JsonIgnore annotation to simply ignore one of the sides of the relationship, thus braking the chain.
In the following example – we will prevent the infinite recursion by ignoring the “User” property “userItems” from serialization:
Here is “User” entity:
public class User { public int id; public String name; @JsonIgnore public List<Item> userItems; }
And here is our 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"))); }
And here is the output of serialization:
{ "id":2, "itemName":"book", "owner": { "id":1, "name":"John" } }
6. 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 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<Item> userItems; }
And here is the “CustomListSerializer“:
public class CustomListSerializer extends JsonSerializer<List<Item>>{ @Override public void serialize(List<Item> items, JsonGenerator generator, SerializerProvider provider) throws IOException, JsonProcessingException { List<Integer> ids = new ArrayList<Integer>(); 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] } }
7. 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().reader(ItemWithIdentity.class).readValue(json); assertEquals(2, item.id); assertEquals("book", item.itemName); assertEquals("John", item.owner.name); }
8. 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 is “User” entity:
public class User { public int id; public String name; @JsonDeserialize(using = CustomListDeserializer.class) public List<Item> userItems; }
And here is our “CustomListDeserializer“:
public class CustomListDeserializer extends JsonDeserializer<List<Item>>{ @Override public List<Item> deserialize(JsonParser jsonparser, DeserializationContext context) throws IOException, JsonProcessingException { return new ArrayList<Item>(); } }
And here is our 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().reader(Item.class).readValue(json); assertEquals(2, item.id); assertEquals("book", item.itemName); assertEquals("John", item.owner.name); }
9. Conclusion
We learned how serialize/deserialize entities with bidirectional relationship using Jackson.
You can find the source code here.