Infinite Recursion with Jackson JSON and Hibernate JPA issue

前端 未结 25 3129
你的背包
你的背包 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:39

    VERY IMPORTANT: If you are using LOMBOK, make shure to exclude attributes of collections like Set, List, etc...

    Like this:

    @EqualsAndHashCode(exclude = {"attributeOfTypeList", "attributeOfTypeSet"})
    
    0 讨论(0)
  • 2020-11-21 07:41

    Now Jackson supports avoiding cycles without ignoring the fields:

    Jackson - serialization of entities with birectional relationships (avoiding cycles)

    0 讨论(0)
  • 2020-11-21 07:43

    You can use @JsonIgnore, but this will ignore the json data which can be accessed because of the Foreign Key relationship. Therefore if you reqiure the foreign key data (most of the time we require), then @JsonIgnore will not help you. In such situation please follow the below solution.

    you are getting Infinite recursion, because of the BodyStat class again referring the Trainee object

    BodyStat

    @ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
    @JoinColumn(name="trainee_fk")
    private Trainee trainee;
    

    Trainee

    @OneToMany(mappedBy = "trainee", fetch = FetchType.EAGER, cascade = CascadeType.ALL)
    @Column(nullable = true)
    private Set<BodyStat> bodyStats;
    

    Therefore, you have to comment/omit the above part in Trainee

    0 讨论(0)
  • 2020-11-21 07:45

    Be sure you use com.fasterxml.jackson everywhere. I spent much time to find it out.

    <properties>
      <fasterxml.jackson.version>2.9.2</fasterxml.jackson.version>
    </properties>
    
    <!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-annotations -->
    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-annotations</artifactId>
        <version>${fasterxml.jackson.version}</version>
    </dependency>
    
    <!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
        <version>${fasterxml.jackson.version}</version>
    </dependency>
    

    Then use @JsonManagedReference and @JsonBackReference.

    Finally, you can serialize your model to JSON:

    import com.fasterxml.jackson.databind.ObjectMapper;
    
    ObjectMapper mapper = new ObjectMapper();
    String json = mapper.writeValueAsString(model);
    
    0 讨论(0)
  • 2020-11-21 07:49

    This worked perfectly fine for me. Add the annotation @JsonIgnore on the child class where you mention the reference to the parent class.

    @ManyToOne
    @JoinColumn(name = "ID", nullable = false, updatable = false)
    @JsonIgnore
    private Member member;
    
    0 讨论(0)
  • 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;
    
    0 讨论(0)
提交回复
热议问题