In this post,
I am going to explain how the bidirectional relationships are handled in
Jackson.
Let’s take
an example of simple one-to-many relationship (User-to-DeliveredItem).
Model
classes looks like below.
public class User {
private int id;
private String firstName;
private String lastName;
List<DeliveredItem> deliveredItems;
.....
.....
.....
}
public class DeliveredItem {
private int itemId;
private String itemName;
private User deliveredTo;
.....
.....
.....
}
When you
try to Serialize User entity, you will end up in StackOverflowError.
User user1 = new User();
user1.setId(1);
user1.setFirstName("Ram");
user1.setLastName("Gurram");
DeliveredItem item1 = new DeliveredItem();
item1.setDeliveredTo(user1);
item1.setItemId(1);
item1.setItemName("Sofa Set");
user1.setDeliveredItems(new ArrayList () {
{
add(item1);
}
});
ObjectMapper objectMapper = new ObjectMapper();
String json = objectMapper.writeValueAsString(user1);
How to
resolve StackOverflowError?
Solution
1: Using
@JsonManagedReference, @JsonBackReference annotations, we can get resolve these
errors.
@JsonManagedReference
It
represents the forward part of reference. If you annotate any bidirectional
linking field with this annotation, then the field gets serialized normally.
@JsonBackReference
It
represents the back part of reference. If you annotate any bidirectional
linking field with this annotation, then it will be omitted from serialization.
public class User {
private int id;
private String firstName;
private String lastName;
@JsonManagedReference
List<DeliveredItem> deliveredItems;
.....
.....
}
public class DeliveredItem {
private int itemId;
private String itemName;
@JsonBackReference
private User deliveredTo;
.....
.....
}
Find the
below working application.
package com.sample.app.model;
import com.fasterxml.jackson.annotation.JsonBackReference;
public class DeliveredItem {
private int itemId;
private String itemName;
@JsonBackReference
private User deliveredTo;
public int getItemId() {
return itemId;
}
public void setItemId(int itemId) {
this.itemId = itemId;
}
public String getItemName() {
return itemName;
}
public void setItemName(String itemName) {
this.itemName = itemName;
}
public User getDeliveredTo() {
return deliveredTo;
}
public void setDeliveredTo(User deliveredTo) {
this.deliveredTo = deliveredTo;
}
}
User.java
package com.sample.app.model;
import java.util.List;
import com.fasterxml.jackson.annotation.JsonBackReference;
import com.fasterxml.jackson.annotation.JsonManagedReference;
public class User {
private int id;
private String firstName;
private String lastName;
@JsonManagedReference
List<DeliveredItem> deliveredItems;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public List<DeliveredItem> getDeliveredItems() {
return deliveredItems;
}
public void setDeliveredItems(List<DeliveredItem> deliveredItems) {
this.deliveredItems = deliveredItems;
}
}
App.java
package com.sample.app;
import java.util.ArrayList;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.sample.app.model.DeliveredItem;
import com.sample.app.model.User;
public class App {
public static void main(String args[]) throws JsonProcessingException {
User user1 = new User();
user1.setId(1);
user1.setFirstName("Ram");
user1.setLastName("Gurram");
DeliveredItem item1 = new DeliveredItem();
item1.setDeliveredTo(user1);
item1.setItemId(1);
item1.setItemName("Sofa Set");
user1.setDeliveredItems(new ArrayList () {
{
add(item1);
}
});
ObjectMapper objectMapper = new ObjectMapper();
String json = objectMapper.writeValueAsString(user1);
System.out.println(json);
}
}
Output
{"id":1,"firstName":"Ram","lastName":"Gurram","deliveredItems":[{"itemId":1,"itemName":"Sofa
Set"}]}
Solution
2: Using @JsonIdentityInfo
@JsonIdentityInfo
is used to solve circular reference of an object by serializing the
backreference identifier rather than fully serializing the object.
@JsonIdentityInfo
serialize the POJO by it's id when it is encountered second time during
serialization.
@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "itemId")
public class DeliveredItem {
private int itemId;
private String itemName;
private User deliveredTo;
.....
.....
}
@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
public class User {
private int id;
private String firstName;
private String lastName;
List<DeliveredItem> deliveredItems;
.....
.....
}
Find the
below working application.
package com.sample.app.model;
import com.fasterxml.jackson.annotation.JsonIdentityInfo;
import com.fasterxml.jackson.annotation.ObjectIdGenerators;
@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "itemId")
public class DeliveredItem {
private int itemId;
private String itemName;
private User deliveredTo;
public int getItemId() {
return itemId;
}
public void setItemId(int itemId) {
this.itemId = itemId;
}
public String getItemName() {
return itemName;
}
public void setItemName(String itemName) {
this.itemName = itemName;
}
public User getDeliveredTo() {
return deliveredTo;
}
public void setDeliveredTo(User deliveredTo) {
this.deliveredTo = deliveredTo;
}
}
User.java
package com.sample.app.model;
import java.util.List;
import com.fasterxml.jackson.annotation.JsonIdentityInfo;
import com.fasterxml.jackson.annotation.ObjectIdGenerators;
@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
public class User {
private int id;
private String firstName;
private String lastName;
List<DeliveredItem> deliveredItems;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public List<DeliveredItem> getDeliveredItems() {
return deliveredItems;
}
public void setDeliveredItems(List<DeliveredItem> deliveredItems) {
this.deliveredItems = deliveredItems;
}
}
When you
ran App.java, you will get below messages in console.
{"id":1,"firstName":"Ram","lastName":"Gurram","deliveredItems":[{"itemId":1,"itemName":"Sofa
Set","deliveredTo":1}]}
From the
output, you can confirm that the id of user is taken in serialization.
Solution
3: Using @JsonIgnore
Just
ignore one side of circular relation.
public class DeliveredItem {
private int itemId;
private String itemName;
@JsonIgnore
private User deliveredTo;
.....
.....
}
Solution
4: Using @JsonView
Use JsonViews
to get more control on serialization and deserialization.
Views.java
package com.sample.app.model;
public class Views {
public static class Public {
}
public static class Internal extends Public {
}
}
Apply the
Views on DeliveredItem class.
public class DeliveredItem {
@JsonView(Views.Public.class)
private int itemId;
@JsonView(Views.Public.class)
private String itemName;
@JsonView(Views.Internal.class)
private User deliveredTo;
.....
.....
}
Use Views
while serializing the data.
String
json =
objectMapper.writerWithView(Views.Public.class).writeValueAsString(user1);
Find the
below working application.
DeliveredItem.java
package com.sample.app.model;
import com.fasterxml.jackson.annotation.JsonView;
public class DeliveredItem {
@JsonView(Views.Public.class)
private int itemId;
@JsonView(Views.Public.class)
private String itemName;
@JsonView(Views.Internal.class)
private User deliveredTo;
public int getItemId() {
return itemId;
}
public void setItemId(int itemId) {
this.itemId = itemId;
}
public String getItemName() {
return itemName;
}
public void setItemName(String itemName) {
this.itemName = itemName;
}
public User getDeliveredTo() {
return deliveredTo;
}
public void setDeliveredTo(User deliveredTo) {
this.deliveredTo = deliveredTo;
}
}
App.java
package com.sample.app;
import java.util.ArrayList;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.sample.app.model.DeliveredItem;
import com.sample.app.model.User;
import com.sample.app.model.Views;
public class App {
public static void main(String args[]) throws JsonProcessingException {
User user1 = new User();
user1.setId(1);
user1.setFirstName("Ram");
user1.setLastName("Gurram");
DeliveredItem item1 = new DeliveredItem();
item1.setDeliveredTo(user1);
item1.setItemId(1);
item1.setItemName("Sofa Set");
user1.setDeliveredItems(new ArrayList () {
{
add(item1);
}
});
ObjectMapper objectMapper = new ObjectMapper();
String json = objectMapper.writerWithView(Views.Public.class).writeValueAsString(user1);
System.out.println(json);
}
}
Run
App.java, you will see below messages in console.
{"id":1,"firstName":"Ram","lastName":"Gurram","deliveredItems":[{"itemId":1,"itemName":"Sofa
Set"}]}
Solution
5: Use Custom
Serializer and Deserializers.
https://self-learning-java-tutorial.blogspot.com/2020/01/jackson-custom-serializer.html
https://self-learning-java-tutorial.blogspot.com/2020/01/jackson-jsonserialize-custom.html
No comments:
Post a Comment