spring-projects / spring-data-rest

Simplifies building hypermedia-driven REST web services on top of Spring Data repositories
https://spring.io/projects/spring-data-rest
Apache License 2.0
916 stars 562 forks source link

Lombok generated constructor not found for deserialization on POST requests [DATAREST-884] #1254

Open spring-projects-issues opened 8 years ago

spring-projects-issues commented 8 years ago

Kai Tödter opened DATAREST-884 and commented

With Spring Boot 1.3.7 the following is working, with Spring Boot 1.4.0 not anymore: You find all the examples in this report at https://github.com/toedter/chatty/ Branches: master -> Spring Boot 1.3.7 => working Spring-Boot-1.4.0 -> Spring Boot 1.4.0 => not working

Consider 2 REST resources with repositories: User and ChatMessage ChatMessage has a relation to user: @ManyToOne private User author;

With Spring Boot 1.3.7 it was possible to create a new ChatMessage including a relation to an existing user with one POST request, when http://localhost:8080/api/users/toedter_k points to a valid User resource :

curl 'http://localhost:8080/api/messages' -i -X POST -H 'Content-Type: application/hal+json' -d '{"author":"http://localhost:8080/api/users/toedter_k","text":"Hello!"}'

This call gives the following error when using Spring Boot 1.4.0: o.s.d.r.w.RepositoryRestExceptionHandler : Could not read document: Can not construct instance of com.toedter.chatty.server.boot.user.User: no String-argument constructor/factory method to deserialize from String value ('http://localhost:8080/api/users/toedter_k')

You find the corresponding test (Spring Restdocs) at https://github.com/toedter/chatty/blob/master/subprojects/com.toedter.chatty.server.boot/src/test/java/com/toedter/chatty/server/boot/ApiDocumentation.java

    @Test
    public void messagesCreateExample() throws Exception {
        Map<String, String> user = new HashMap<String, String>();
        user.put("id", "toedter_k");
        user.put("fullName", "toedter_k");
        user.put("email", "kai@toedter.com");

        String userLocation = this.mockMvc
                .perform(
                        post("/api/users").contentType(MediaTypes.HAL_JSON).content(
                                this.objectMapper.writeValueAsString(user)))
                .andExpect(status().isCreated()).andReturn().getResponse()
                .getHeader("Location");

        Map<String, Object> chatMessage = new HashMap<String, Object>();
        chatMessage.put("text", "Hello!");
        chatMessage.put("author", userLocation);

        this.mockMvc.perform(
                post("/api/messages").contentType(MediaTypes.HAL_JSON).content(
                        this.objectMapper.writeValueAsString(chatMessage))).andExpect(
                status().isCreated())
                .andDo(document("messages-create-example",
                        requestFields(
                                fieldWithPath("text").description("The text of the chat message"),
                                fieldWithPath("author").description("The author of the chat message. This must be the URL to an existing user resource."))));
    }

Affects: 2.5.2 (Hopper SR2), 2.5.3 (Hopper SR3)

1 votes, 4 watchers

spring-projects-issues commented 8 years ago

Oliver Drotbohm commented

This seems to be an issue between Jackson and Lombok. @AllArgsConstructor generates explicit constructor name annotations that cause trouble as fro some reason that causes Jackson to treat the properties slightly different and not apply our registered deserialization customization. You can work around this by changing @AllArgsConstructor to @AllArgsConstructor(suppressConstructorProperties = true). That makes your test go green for me.

I've already filed a related ticket (same effect but slightly different setup) here

spring-projects-issues commented 8 years ago

Kai Tödter commented

Thanks, Oliver. @AllArgsConstructor(suppressConstructorProperties = true) works like a charm!

spring-projects-issues commented 8 years ago

Kai Tödter commented

With Spring Boot 1.4.1 @AllArgsConstructor is working again

spring-projects-issues commented 7 years ago

Robert Rackl commented

I have the same problem. I am expsoing a JpaRepositry as spring-data-rest-hateoas REST service with the "@RepositoryRestResource" anotation.

I also have the parent-child relation between an "idea" in a given "area", ie. idea references area @ManyToOne.

When I try to POST a new idea that references an already existing area like this

{
   "title": "Inserted Idea",
   "description": "Very nice description",
   "area": "/liquido/v2/areas/2"
}

I also get the error message:

Can not construct instance of org.doogie.liquido.model.AreaModel: no String-argument constructor/factory method to deserialize from String value ('/liquido/v2/areas/2')

SOLVED!

The solution is as you mentioned. But interestingly you have to add the (suppressConstructorProperties = true) to the Idea entity. Not the AreaModel as the error message would point to. So you have to suppress Lombok's constructor property on the child entity!

@Data
@Entity
@NoArgsConstructor
@RequiredArgsConstructor(suppressConstructorProperties = true)
public class IdeaModel {
  [...]
}

SideRemark: It works the same with Lomboks AllArgsConstructor or RequiredArgsConstructor

spring-projects-issues commented 7 years ago

Robert Rackl commented

I think this is related: https://jira.spring.io/browse/DATAREST-687