Deserialize JSON with spring : Unresolved forward references Jackson Exception

10,537

Solution 1

Ok I found the problem.

Finally I need to add the resolver. So I find here an example of implement ObjectIDResolver :

import com.fasterxml.jackson.annotation.ObjectIdGenerator;
import com.fasterxml.jackson.annotation.ObjectIdResolver;

import javax.persistence.EntityManager;

/**
 * @author fta on 20.12.15.
 */

public class EntityIdResolver
        implements ObjectIdResolver {

    private EntityManager entityManager;

    public EntityIdResolver(
            final EntityManager entityManager) {

        this.entityManager = entityManager;

    }

    @Override
    public void bindItem(
            final ObjectIdGenerator.IdKey id,
            final Object pojo) {

    }

    @Override
    public Object resolveId(final ObjectIdGenerator.IdKey id) {

        return this.entityManager.find(id.scope, id.key);
    }

    @Override
    public ObjectIdResolver newForDeserialization(final Object context) {

        return this;
    }

    @Override
    public boolean canUseFor(final ObjectIdResolver resolverType) {

        return false;
    }

}

And next I add in @JsonIdentityInfo:

@Entity
@JsonIdentityInfo(
        generator = ObjectIdGenerators.PropertyGenerator.class,
        property = "idAgence",
        resolver = EntityIdResolver.class,
        scope=Agence.class)
public class Agence {
    // ... 
}

Solution 2

You seem to have a cyclical object graph due to your one-to-many relationship. For those you may use the JSOG serializer plugin for Jackson.

It supposedly handles both serialization and deserialization of cyclical object graphs (since Jackson 2.5.1)

@JsonIdentityInfo(generator=JSOGGenerator.class)
public class Person {
    String name;
    Person secretSanta;
}
Share:
10,537
Admin
Author by

Admin

Updated on June 07, 2022

Comments

  • Admin
    Admin almost 2 years

    I work on API Rest project with Spring. I have a service "CreateMateriel" which takes as parameter data JSON :

    JSON of Materiel Object

    {
      "agence": 1, 
      "code": "001",
      "type": "MyType"
    }
    

    "Materiel" has a relation Many to One with "Agence". I put a @JsonIdentityInfo tag to use Agence's Id and not Agence's Object (After seeing this topic)

    @JsonIdentityInfo(
            generator = ObjectIdGenerators.PropertyGenerator.class,
            property = "idAgence")
    @JsonIdentityReference(alwaysAsId = true)
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "agence")
    private Agence agence;
    

    But when I send JSON on POST /materiels I have this exception:

    2017-05-16 18:00:53.021  WARN 8080 --- [nio-8080-exec-8] .w.s.m.s.DefaultHandlerExceptionResolver : Failed to read HTTP message: org.springframework.http.converter.HttpMessageNotReadableException: Could not read document: Unresolved forward references for: 
     at [Source: java.io.PushbackInputStream@767dfd7a; line: 5, column: 1]Object id [1] (for fr.app.domain.Agence) at [Source: java.io.PushbackInputStream@767dfd7a; line: 2, column: 14].; nested exception is com.fasterxml.jackson.databind.deser.UnresolvedForwardReference: Unresolved forward references for: 
     at [Source: java.io.PushbackInputStream@767dfd7a; line: 5, column: 1]Object id [1] (for fr.app.domain.Agence) at [Source: java.io.PushbackInputStream@767dfd7a; line: 2, column: 14].
    

    After many research I have seen the use of ObjectIdResolver in JsonIdentityInfo ... But I think this is not the best solution. This is why I ask for your help in detecting the source of the problem. Thank's

    MaterielController.java

    package fr.app.controllers;
    
    import fr.app.domain.Materiel;
    import fr.app.services.MaterielService;
    import org.springframework.web.bind.annotation.*;
    
    import javax.annotation.Resource;
    import java.util.Collection;
    
    @RestController @RequestMapping(value = "/materiels")
    public class MaterielController {
        @Resource
        private MaterielService materielService;
    
        @RequestMapping(method = RequestMethod.POST)
        public Materiel createMateriel(@RequestBody Materiel materiel) {
            return this.materielService.createMateriel(materiel);
        }
    
        @RequestMapping(method = RequestMethod.GET)
        public Collection<Materiel> getAllMateriels() {
            return this.materielService.getAllMateriels();
        }
    
        @RequestMapping(value = "/{code}", method = RequestMethod.GET)
        public Materiel getMaterielByCode(@PathVariable(value = "code") String code) {
            //find materiel by code
            return this.materielService.getMaterielByCode(code);
    
        }
    
        @RequestMapping(value = "/{id}", method = RequestMethod.DELETE)
        public void deleteMateriel(@PathVariable(value = "id") Long id) {
            this.materielService.deleteMateriel(id);
    
        }
    
        @RequestMapping(value = "/{id}", method = RequestMethod.PUT)
        public Materiel updateMateriel(@PathVariable(value = "id") Long id, @RequestBody
                Materiel materiel) {
    
                materiel.setIdMateriel(id);
    
            return this.materielService.updateMateriel(materiel);
    
        }
    }
    

    Materiel.java

    package fr.app.domain;
    
    import com.fasterxml.jackson.annotation.*;
    
    import javax.persistence.*;
    
    @Entity
    public class Materiel {
        @Id
        @GeneratedValue(strategy = GenerationType.AUTO)
        private Long idMateriel;
    
        @Column(name = "type_materiel", nullable = false)
        private String type;
    
        @Column(name = "code_materiel", unique = true, nullable = false)
        private String code;
    
        @JsonIdentityInfo(
                generator = ObjectIdGenerators.PropertyGenerator.class,
                property = "idAgence")
        @ManyToOne(fetch = FetchType.LAZY)
        @JoinColumn(name = "agence")
        private Agence agence;
    
        public Materiel() {    }
    
        public Materiel(String type, String code, String dateScan) {
            this.type = type;
            this.code = code;
            this.dateScan = dateScan;
        }
    
        public Long getIdMateriel() {
            return idMateriel;
        }
    
        public String getType() {
            return type;
        }
    
        public void setType(String type) {
            this.type = type;
        }
    
        public String getCode() {
            return code;
        }
    
        public void setCode(String code) {
            this.code = code;
        }
    
        public Agence getAgence() {
            return agence;
        }
    
        public void setAgence(Agence agence) {
            if(this.agence != null)
                this.agence.deleteMateriel(this);
            this.agence = agence;
            this.agence.addMateriel(this);
        }
    }
    

    MaterielService.java

    package fr.app.services;
    
    import fr.app.domain.Materiel;
    import fr.app.repositories.MaterielRepository;
    import org.apache.commons.collections.IteratorUtils;
    import org.springframework.stereotype.Service;
    
    import javax.annotation.Resource;
    import java.util.Collection;
    
    @Service(value = "materielService")
    public class MaterielServiceImpl implements MaterielService {
        @Resource
        private MaterielRepository materielRepository;
    
        ...
    
        @Override
        public Materiel createMateriel(Materiel materiel) {
            return this.materielRepository.save(materiel);
        }
    
       ...
    }
    

    Agence.java

    package fr.app.domain;
    
    import com.fasterxml.jackson.annotation.*;
    
    import javax.persistence.*;
    import java.util.HashSet;
    import java.util.Set;
    
    @Entity
    public class Agence {
        @Id
        @GeneratedValue(strategy = GenerationType.AUTO)
        private Long idAgence;
    
        @Column(name = "nom",unique = true, nullable = false)
        private String nom;
    
        @OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "agence")
        private Set<Materiel> materiels = new HashSet<Materiel>();
    
        public Agence() { }
    
        public Agence(String nom) {
            this.nom = nom;
        }
    
        public Long getIdAgence() {
            return idAgence;
        }
    
        public String getNom() {
            return nom;
        }
    
        public void setNom(String nom) {
            this.nom = nom;
        }
    
        public Set<Materiel> getMateriels() {
            return materiels;
        }
    
        public void setMateriels(Set<Materiel> materiels) {
            this.materiels = materiels;
        }
    
        public void addMateriel(Materiel materiel) {
            this.materiels.add(materiel);
        }
    
        public void deleteMateriel(Materiel materiel) {
            this.materiels.remove(materiel);
        }
    
    }
    

    pom.xml

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.6</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>commons-collections</groupId>
            <artifactId>commons-collections</artifactId>
            <version>3.0</version>
        </dependency>
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-core</artifactId>
            <version>5.2.9.Final</version>
        </dependency>
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-entitymanager</artifactId>
            <version>5.2.9.Final</version>
        </dependency>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>2.6.1</version>
        </dependency>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
            <version>2.2.2</version>
            <scope>compile</scope>
        </dependency>
    
        <!-- Needed for JSON View -->
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.8.6</version>
        </dependency>
    </dependencies>