Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to POST nested entities with Spring Data REST

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*
like image 375
73nko Avatar asked Jul 04 '14 07:07

73nko


3 Answers

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.

like image 55
Gauthier JACQUES Avatar answered Nov 01 '22 19:11

Gauthier JACQUES


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.

like image 43
TLA Avatar answered Nov 01 '22 21:11

TLA


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"
}
like image 4
Sam Berry Avatar answered Nov 01 '22 21:11

Sam Berry