Close

Jackson JSON - Using @JsonIdentityInfo to handle circular references

[Updated: Oct 24, 2018, Created: Sep 18, 2018]

@JsonIdentityInfo is used to handle circular reference of an object by serializing the back-reference's identifier rather than serializing the complete reference.

@JsonIdentityInfo allows to serialize a POJO by id when it is encountered second time during serialization.

@JsonIdentityInfo annotation:

package com.fasterxml.jackson.annotation;
  ..
public @interface JsonIdentityInfo{
    //Name of JSON property in which Object Id will reside
    public String property() default "@id";

    //Generator to use for producing Object Identifier for objects.
    //Pre-defined or custom generators can be used 
    public Class<? extends ObjectIdGenerator<?>> generator();

    //Resolver to use for producing POJO from Object Identifier.
    public Class<? extends ObjectIdResolver> resolver() default SimpleObjectIdResolver.class;

    //Scope is used to define applicability of an Object Id
    public Class<?> scope() default Object.class;
}

ObjectIdGenerator predefined implementation

Following implementations are defined in ObjectIdGenerators class:

Example

POJOs

@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class,
      property = "id")
public class Customer {
  private int id;
  private String name;
  private Order order;
    .............
}
@JsonIdentityInfo#generator=ObjectIdGenerator.class is used to produce the Identifier for the circular reference.
The attribute @JsonIdentityInfo#property specifies the property name representing the id of the target reference. In above example 'id' refers to the Customer#id which should be used for Order#customer back-reference while serializing/deserializing.
public class Order {
  private int orderId;
  private List<Integer> itemIds;
  private Customer customer;
    .............
}

Main class

public class ExampleMain {
  public static void main(String[] args) throws IOException {
      Order order = new Order();
      order.setOrderId(1);
      order.setItemIds(List.of(10, 30));

      Customer customer = new Customer();
      customer.setId(2);
      customer.setName("Peter");
      customer.setOrder(order);
      order.setCustomer(customer);

      System.out.println(customer);
      System.out.println("-- serializing --");
      ObjectMapper om = new ObjectMapper();
      String s = om.writeValueAsString(customer);
      System.out.println(s);
      System.out.println("-- deserializing --");
      Customer customer2 = om.readValue(s, Customer.class);
      System.out.println(customer2);
  }
}
Customer{id=2, name='Peter', order=Order{id=1, itemIds=[10, 30]}}
-- serializing --
{"id":2,"name":"Peter","order":{"orderId":1,"itemIds":[10,30],"customer":2}}
-- deserializing --
Customer{id=2, name='Peter', order=Order{id=1, itemIds=[10, 30]}}

Without @JsonIdentityInfo

If we don't use @JsonIdentityInfo on our Customer class then output will be:

Customer{id=2, name='Peter', order=Order{id=1, itemIds=[10, 30]}}
-- serializing --
Exception in thread "main" com.fasterxml.jackson.databind.JsonMappingException: Infinite recursion (StackOverflowError) (through reference chain: com.logicbig.example.Customer["order"]->com.logicbig.example.Order["customer"]->com.logicbig.example.Customer["order"]->com.logicbig.example.Order["customer"]->com.logicbig.example.Customer["order"]->com.logicbig.example.Order["customer"]->com.logicbig.example.Customer["order"]->com.logicbig.example.Order["customer"]->com.logicbig.example.Customer["order"]->com.logicbig.example.Order["customer"]->com.logicbig.example.Customer["order"]-> .........................
	at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:734)
	at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:155)
	at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:727)
	at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:719)
	at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:155)
..................

References involving OneToMany/ManyToMany relations

@JsonIdentityInfo can also be used for the circular references involving collections. For example, following shows the use of many-to-many circular references:

@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class,
      property = "empId")
public class Employee {
  private int empId;
  private List<Dept> depts;
    .............
}
public class Dept {
  private int deptId;
  List<Employee> employees;
    .............
}
public class ExampleMain2 {

    public static void main(String[] args) throws IOException {
        Employee emp = new Employee();
        emp.setEmpId(1);

        Dept dept1 = new Dept();
        dept1.setDeptId(3);
        dept1.setEmployees(List.of(emp));

        Dept dept2 = new Dept();
        dept2.setDeptId(4);
        dept2.setEmployees(List.of(emp));

        System.out.println("-- before serializing --");
        System.out.println(emp);

        System.out.println("-- json string after serializing --");
        ObjectMapper om = new ObjectMapper();
        String s = om.writeValueAsString(emp);
        System.out.println(s);
        System.out.println("-- deserializing --");
        Employee employee = om.readValue(s, Employee.class);
        System.out.println(employee);
        System.out.println("-- deserialized back references --");
        for (Dept dept : employee.getDepts()) {
            System.out.println(dept + " -> " + dept.getEmployees());
        }
    }
}
-- before serializing --
Employee{empId=1, depts=[Dept{deptId=3}, Dept{deptId=4}]}
-- json string after serializing --
{"empId":1,"depts":[{"deptId":3,"employees":[1]},{"deptId":4,"employees":[1]}]}
-- deserializing --
Employee{empId=1, depts=[Dept{deptId=3}, Dept{deptId=4}]}
-- deserialized back references --
Dept{deptId=3} -> [Employee{empId=1, depts=[Dept{deptId=3}, Dept{deptId=4}]}]
Dept{deptId=4} -> [Employee{empId=1, depts=[Dept{deptId=3}, Dept{deptId=4}]}]

Example Project

Dependencies and Technologies Used:

  • jackson-databind 2.9.6: General data-binding functionality for Jackson: works on core streaming API.
  • JDK 10
  • Maven 3.5.4

@JsonIdentityInfo Example Select All Download
  • jackson-json-identity-info-example
    • src
      • main
        • java
          • com
            • logicbig
              • example
                • Customer.java

    See Also