Deserialize JSON with spring : Unresolved forward references Jackson Exception
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;
}
Admin
Updated on June 07, 2022Comments
-
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>