When trying to convert a JPA object that has a bi-directional association into JSON, I keep getting
org.codehaus.jackson.map.JsonMappingException: Infinite
For me the best solution is to use @JsonView
and create specific filters for each scenario. You could also use @JsonManagedReference
and @JsonBackReference
, however it is a hardcoded solution to only one situation, where the owner always references the owning side, and never the opposite. If you have another serialization scenario where you need to re-annotate the attribute differently, you will not be able to.
Lets use two classes, Company
and Employee
where you have a cyclic dependency between them:
public class Company {
private Employee employee;
public Company(Employee employee) {
this.employee = employee;
}
public Employee getEmployee() {
return employee;
}
}
public class Employee {
private Company company;
public Company getCompany() {
return company;
}
public void setCompany(Company company) {
this.company = company;
}
}
And the test class that tries to serialize using ObjectMapper
(Spring Boot):
@SpringBootTest
@RunWith(SpringRunner.class)
@Transactional
public class CompanyTest {
@Autowired
public ObjectMapper mapper;
@Test
public void shouldSaveCompany() throws JsonProcessingException {
Employee employee = new Employee();
Company company = new Company(employee);
employee.setCompany(company);
String jsonCompany = mapper.writeValueAsString(company);
System.out.println(jsonCompany);
assertTrue(true);
}
}
If you run this code, you'll get the:
org.codehaus.jackson.map.JsonMappingException: Infinite recursion (StackOverflowError)
@JsonView
enables you to use filters and choose what fields should be included while serializing the objects. A filter is just a class reference used as a identifier. So let's first create the filters:
public class Filter {
public static interface EmployeeData {};
public static interface CompanyData extends EmployeeData {};
}
Remember, the filters are dummy classes, just used for specifying the fields with the @JsonView
annotation, so you can create as many as you want and need. Let's see it in action, but first we need to annotate our Company
class:
public class Company {
@JsonView(Filter.CompanyData.class)
private Employee employee;
public Company(Employee employee) {
this.employee = employee;
}
public Employee getEmployee() {
return employee;
}
}
and change the Test in order for the serializer to use the View:
@SpringBootTest
@RunWith(SpringRunner.class)
@Transactional
public class CompanyTest {
@Autowired
public ObjectMapper mapper;
@Test
public void shouldSaveCompany() throws JsonProcessingException {
Employee employee = new Employee();
Company company = new Company(employee);
employee.setCompany(company);
ObjectWriter writter = mapper.writerWithView(Filter.CompanyData.class);
String jsonCompany = writter.writeValueAsString(company);
System.out.println(jsonCompany);
assertTrue(true);
}
}
Now if you run this code, the Infinite Recursion problem is solved, because you have explicitly said that you just want to serialize the attributes that were annotated with @JsonView(Filter.CompanyData.class)
.
When it reaches the back reference for company in the Employee
, it checks that it's not annotated and ignore the serialization. You also have a powerful and flexible solution to choose which data you want to send through your REST APIs.
With Spring you can annotate your REST Controllers methods with the desired @JsonView
filter and the serialization is applied transparently to the returning object.
Here are the imports used in case you need to check:
import static org.junit.Assert.assertTrue;
import javax.transaction.Transactional;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectWriter;
import com.fasterxml.jackson.annotation.JsonView;