How to POST nested entities with Spring Data REST
Solution 1
Try adding @RestResource(exported = false)
on field answers
in class Questionary
.
According to me, this error occurs because the deserializer expects URIs to fetch the answers from, instead of having the answers nested in the JSON. Adding the annotation tells it to look in JSON instead.
Solution 2
I'm still seeing this error with 2.3.0.M1, but I finally found a workaround.
The basic issue is this: If you post the url of the embedded entity in the JSON, it works. If you post the actual embedded entity JSON, it doesn't. It tries to deserialize the entity JSON into a URI, which of course fails.
It looks like the issue is with the two TypeConstrainedMappingJackson2HttpMessageConverter objects that spring data rest creates in its configuration (in RepositoryRestMvcConfiguration.defaultMessageConverters()).
I finally got around the issue by configuring the supported media types of the messageConverters so that it skips those two and hits the plain MappingJackson2HttpMessageConverter, which works fine with nested entities.
For example, if you extend RepositoryRestMvcConfiguration and add this method, then when you send a request with content-type of 'application/json', it will hit the plain MappingJackson2HttpMessageConverter instead of trying to deserialize into URIs:
@Override
public void configureHttpMessageConverters(List<HttpMessageConverter<?>> messageConverters) {
((MappingJackson2HttpMessageConverter) messageConverters.get(0))
.setSupportedMediaTypes(asList(MediaTypes.HAL_JSON));
((MappingJackson2HttpMessageConverter) messageConverters.get(2))
.setSupportedMediaTypes(asList(MediaType.APPLICATION_JSON));
}
That configures the message converters produced by defaultMessageConverters() in RepositoryRestMvcConfiguration.
Keep in mind that the plain objectMapper can't handle URIs in the JSON - you'll still need to hit one of the two preconfigured message converters any time you pass URIs of embedded entities.
Solution 3
One issue with your JSON is that you are trying to deserialize a string as a question:
"question": "http://localhost:8080/question/6"
In your Answer
object, Jackson is expecting an object for question. It appears that you are using URLs for IDs, so instead of a string you need to pass something like this for your question:
"question": {
"id": "http://localhost:8080/question/6"
}
73nko
Updated on June 04, 2022Comments
-
73nko almost 2 years
I'm building a Spring Data REST application and I'm having some problems when I try to POST it. The main entity has other two related entities nested.
There is a "questionary" object which has many answers and each one of these answers have many replies.
I generate a JSON like this from the front application to POST the questionary:
{ "user": "http://localhost:8080/users/1", "status": 1, "answers": [ { "img": "urlOfImg", "question": "http://localhost:8080/question/6", "replies": [ { "literal": "http://localhost:8080/literal/1", "result": "6" }, { "literal": "http://localhost:8080/literal/1", "result": "6" } ] }, { "img": "urlOfImg", "question": "http://localhost:8080/question/6", "replies": [ { "literal": "http://localhost:8080/literal/3", "result": "10" } ] } ] }
But when I try to post it, I get the follow error response:
{ "cause" : { "cause" : { "cause" : null, "message" : "Template must not be null or empty!" }, "message" : "Template must not be null or empty! (through reference chain: project.models.Questionary[\"answers\"])" }, "message" : "Could not read JSON: Template must not be null or empty! (through reference chain: project.models.Questionary[\"answers\"]); nested exception is com.fasterxml.jackson.databind.JsonMappingException: Template must not be null or empty! (through reference chain: project.models.Questionary[\"answers\"])" }
Edit:
I also add my repository:
@RepositoryRestResource(collectionResourceRel = "questionaries", path = "questionaries") public interface InspeccionRepository extends JpaRepository<Inspeccion, Integer> { @RestResource(rel="byUser", path="byUser") public List<Questionary> findByUser (@Param("user") User user); }
My Entity Questionary class is :
@Entity @Table(name="QUESTIONARY", schema="enco" ) public class Questionary implements Serializable { private static final long serialVersionUID = 1L; //---------------------------------------------------------------------- // ENTITY PRIMARY KEY ( BASED ON A SINGLE FIELD ) //---------------------------------------------------------------------- @Id @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "SEC_QUESTIONARY") @SequenceGenerator(name = "SEC_QUESTIONARY", sequenceName = "ENCO.SEC_QUESTIONARY", allocationSize = 1) @Column(name="IDQUES", nullable=false) private Integer idques ; //---------------------------------------------------------------------- // ENTITY DATA FIELDS //---------------------------------------------------------------------- @Column(name="ESTATUS") private Integer estatus ; //---------------------------------------------------------------------- // ENTITY LINKS ( RELATIONSHIP ) //---------------------------------------------------------------------- @ManyToOne @JoinColumn(name="IDUSER", referencedColumnName="IDUSER") private User user; @OneToMany(mappedBy="questionary", targetEntity=Answer.class) private List<Answer> answers; //---------------------------------------------------------------------- // CONSTRUCTOR(S) //---------------------------------------------------------------------- public Questionary() { super(); } //---------------------------------------------------------------------- // GETTERS & SETTERS FOR FIELDS //---------------------------------------------------------------------- //--- DATABASE MAPPING : IDNSE ( NUMBER ) public void setIdnse( Integer idnse ) { this.idnse = idnse; } public Integer getIdnse() { return this.idnse; } //--- DATABASE MAPPING : ESTADO ( NUMBER ) public void setEstatus Integer estatus ) { this.estatus = estatus; } public Integer getEstatus() { return this.estatus; } //---------------------------------------------------------------------- // GETTERS & SETTERS FOR LINKS //---------------------------------------------------------------------- public void setUser( Usuario user ) { this.user = user; } public User getUser() { return this.user; } public void setAnswers( List<Respuesta> answers ) { this.answers = answer; } public List<Answer> getAnswers() { return this.answers; } // Get Complete Object method public List<Answer> getAnswerComplete() { List<Answer> answers = this.answers; return answers; } }
My Answer Entity:
@Entity @Table(name="ANSWER", schema="enco" ) public class Answer implements Serializable { private static final long serialVersionUID = 1L; //---------------------------------------------------------------------- // ENTITY PRIMARY KEY ( BASED ON A SINGLE FIELD ) //---------------------------------------------------------------------- @Id @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "SEC_ANSWER") @SequenceGenerator(name = "SEC_ANSWER", sequenceName = "ENCOADMIN.SEC_ANSWER", allocationSize = 1) @Column(name="IDANS", nullable=false) private Integer idans ; //---------------------------------------------------------------------- // ENTITY DATA FIELDS //---------------------------------------------------------------------- @Column(name="IMG", length=100) private String img ; //---------------------------------------------------------------------- // ENTITY LINKS ( RELATIONSHIP ) //---------------------------------------------------------------------- @ManyToOne @JoinColumn(name="IDQUES", referencedColumnName="IDQUES") private Questionary questionary ; @OneToMany(mappedBy="answer", targetEntity=Reply.class) private List<Reply> replies; @ManyToOne @JoinColumn(name="IDQUE", referencedColumnName="IDQUE") private Question Question ; //---------------------------------------------------------------------- // CONSTRUCTOR(S) //---------------------------------------------------------------------- public Answer() { super(); } //---------------------------------------------------------------------- // GETTER & SETTER FOR THE KEY FIELD //---------------------------------------------------------------------- public void setIdans( Integer idans ) { this.idans = idans ; } public Integer getIdans() { return this.idans; } //---------------------------------------------------------------------- // GETTERS & SETTERS FOR FIELDS //---------------------------------------------------------------------- //--- DATABASE MAPPING : IMAGEN ( VARCHAR2 ) public void setImg( String img ) { this.img = img; } public String getImg() { return this.img; } //---------------------------------------------------------------------- // GETTERS & SETTERS FOR LINKS //---------------------------------------------------------------------- public void setQuestionary( Questionary questionary ) { this.questionary = questionary; } public Questionary getQuestionary() { return this.questionary; } public void setReplies( List<Reply> contestaciones ) { this.replies = replies; } public List<Reply> getReplies() { return this.replies; } public void setQuestion( Question question ) { this.question = question; } public Question getQuestion() { return this.question; } }
And this is the error console:
Caused by: com.fasterxml.jackson.databind.JsonMappingException: Template must not be null or empty! (through reference chain: project.models.Questionary["answers"]) at com.fasterxml.jackson.databind.JsonMappingException.wrapWithPath(JsonMappingException.java:232) ~[jackson-databind-2.3.3.jar:2.3.3] at *snip*
-
Michał Ziober almost 10 yearsCould you also show your controller method, stacktrace and
POJO
class? -
73nko almost 10 yearsI've edit the main post with all the information that you request. Thanks for your help.
-
Michał Ziober almost 10 yearsMaybe a problem is in wrong data? Is it ok:
"question": "http://localhost:8080/question/6"
. Maybe it should look like this:"question": 6
. Why are you sendingURL
s instead of ids? -
73nko almost 10 yearsBecause there is a relationship between questions and answers, these part is ok, I've tried it Individualy and works without problem. The issue is when I nest and object inside an other in a list. For example, if I do this post first only the inspection and one post for each answer and another for each reply is working but I thing is absurd when I have a questionary with more than 350 answers an each of them have 3 or 4 replys.... It have to be a way to do it in one nested object...
-
Sam Berry almost 10 yearsYour setter for answers takes a list of Respuesta objects instead of Answer objects, is this a typo or purposeful?
-
73nko almost 10 yearsThat's because I've got the app in Spanish and I've translated all for the post and I didn't see that. Respuesta is the same of Answer ;).
-
brwngrldev almost 10 yearsDid you ever figure this out?
-
JBCP over 9 yearsI believe this page describes the right way to get this working, but when i try the method there, i get the same error you do: github.com/spring-projects/spring-data-rest/wiki/…
-
InsaurraldeAP over 8 yearsSame here, did you solve this?
-
-
73nko almost 10 yearsSam, thanks for your answer but It's still not working. I've got the same error. Any other idea?
-
Jason about 9 yearsDo you mean that you can't POST an object that has one field as an embedded entity and another field as a URI?
-
TLA about 9 yearsYes, until this is fixed that won't work. If you override configureHttpMessageConverters like I suggested, you'll still need to pick one strategy per request - either embed all nested entities as JSON or send URIs for all of them - and set the content-type of the request appropriately so that it hits the correct messageConverter.
-
Jason about 9 yearsDo you happen to know if there is a bug report for this?
-
verystrongjoe over 8 yearsI am using spring-boot-starter-data-rest-1.2.5.RELEASE.jar. But I still have a problem. Which version do I have to upgrade?
-
user1523177 over 8 yearsThis solution worked perfectly for me. After this change, I was able to add non-persisted items to a collection of a one-to-many resources.
-
phil about 8 yearsBut now you can't use the CRUD-methods of your Questionary-repository, because they won't be exposed. Question creation will always handled by the Answer-repository, because on the Questionary-repository it will be not allowed. Is that right?
-
Jacob Barnes over 5 yearsNot sure if this will help anyone, but in the implementation I'm working with
@RestResource(exported = false)
needed to be added to the repository file, e.g. QuestionaryController, QuestionaryModel, QuestionaryRepo. I'm not sure if that is standard in spring or not, I've only worked with it a couple months for a basic backend service, but thought it might help someone in a similar situation.