Infinite Recursion with Jackson JSON and Hibernate JPA issue

前端 未结 25 3215
你的背包
你的背包 2020-11-21 07:31

When trying to convert a JPA object that has a bi-directional association into JSON, I keep getting

org.codehaus.jackson.map.JsonMappingException: Infinite          


        
25条回答
  •  我寻月下人不归
    2020-11-21 07:50

    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.

    Problem

    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)
    

    Solution Using `@JsonView`

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

提交回复
热议问题