Deserialize JSON with spring: Jackson Exception unresolved direct links

I am working on an API Rest project with Spring. I have a CreateMateriel service that accepts JSON parameter data:

JSON object materiel

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

Materiel has a lot to do with the agency. I put the @JsonIdentityInfo tag to use the Agence Id and not the Agence object (after viewing this section )

 @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 to POST / materiels, I have this exception:

 2017-05-16 18:00:53.021 WARN 8080 --- [nio-8080-exec-8] .wsmsDefaultHandlerExceptionResolver : 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 studies, I have seen using ObjectIdResolver in JsonIdentityInfo ... But I think this is not the best solution. This is why I ask you to help identify the source of the problem. thank

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> 
0
java spring jackson json-deserialization
May 16 '17 at 16:44
source share
3 answers

Ok, I found a problem.

Finally, I need to add a recognizer. Therefore, I find here an example implementation of 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 then I will add to @JsonIdentityInfo :

 @Entity @JsonIdentityInfo( generator = ObjectIdGenerators.PropertyGenerator.class, property = "idAgence", resolver = EntityIdResolver.class, scope=Agence.class) public class Agence { // ... } 
+1
May 18 '17 at 1:05 pm
source share
โ€” -

You have two domains: Materiel and Agence. When Jackson tries to deserialize Materiel, he calls the getAgence method. This method returns an Agence object that will also be deserialized by Jackson. When Jackson calls the getMeteriels method (from Agence), he returns the Set of Materiel, which will also be deserialized. The problem is that Jackson will try to deserialize each Materiel in Set again. Same question here .

0
May 16 '17 at 9:00 p.m.
source share

For your last exception (utf-8), you can change the signature of your controller, as shown below:

 @RequestMapping(method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE, consumes = MediaType.APPLICATION_JSON_VALUE) public ResponseEntity<Bean> create(HttpServletRequest request) throws IOException { // getting the posted value String body = CharStreams.toString(request.getReader()); Bean bean = new ObjectMapper().readValue(body, service.getBeanClass()); bean.setId(null); Bean saved = service.save(bean); URI location = ServletUriComponentsBuilder.fromCurrentRequest().path("/{id}").buildAndExpand(saved.getId()).toUri(); return ResponseEntity.created(location).body(saved); } 

With this solution, you have all the control over the display.

Enjoy

0
May 17, '17 at 15:33
source share



All Articles