javajsonjackson

InvalidDefinitionException: "Argument #0 of constructor has no property name"


I have trouble deserializing a JSON. My DTO class has a special private constructor for that. I annotated it with @JsonCreator

@Getter
public class QuestionCommentResponseDto {
    private final Long id;
    private final Long questionId;
    private final LocalDateTime createdDate;
    private final LocalDateTime modifiedDate;
    private final String text;
    @Setter
    private AccountResponseDto owner;

    public QuestionCommentResponseDto(Long id, Long questionId, LocalDateTime createdDate,
                                      LocalDateTime modifiedDate, String text) {
        this.id = id;
        this.questionId = questionId;
        this.createdDate = createdDate;
        this.modifiedDate = modifiedDate;
        this.text = text;
    }
    @JsonCreator
    private QuestionCommentResponseDto(Long id, Long questionId, LocalDateTime createdDate,
                                      LocalDateTime modifiedDate, String text,
                                      AccountResponseDto owner) {
        this(id, questionId, createdDate, modifiedDate, text);
        this.owner = owner;
    }
// equals() and hashcode()
}
// the associated DTO
@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
public class AccountResponseDto {

    private Long id;
    private String username;
// equals() and hashcode()

For some reason, Jackson can't use the designated constructor. The error message is obscure

com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Invalid type definition for type `stack.overflow.model.dto.response.QuestionCommentResponseDto`: Argument #0 of constructor [constructor for `stack.overflow.model.dto.response.QuestionCommentResponseDto` (6 args), annotations: {interface com.fasterxml.jackson.annotation.JsonCreator=@com.fasterxml.jackson.annotation.JsonCreator(mode=DEFAULT)} has no property name (and is not Injectable): can not use as property-based Creator
 at [Source: (String)"{"data":{"id":1,"questionId":1,"createdDate":"2023-06-03T20:16:57.238883","modifiedDate":"2023-06-03T20:16:57.238883","text":"text","owner":{"id":1,"username":"mickey_m"}}}"; line: 1, column: 1]

What does it mean? What is "argument #0"? "id"? How is it problematic? It perfectly matches the field in name and type so I expect Jackson to be smart enough to map them together. And most importantly, how do I fix this problem?

This similar question is for Kotlin so I don't think it's helpful in this case


Solution

  • Unfortunately, Jackson is not smart enough to automatically map JSON's id, text, etc. to your class's fields id, text, and so on. So you have to manually perform the mapping with @JsonProperty annotations like so

        @JsonCreator
        private QuestionCommentResponseDto(@JsonProperty("id") Long id, @JsonProperty("questionId") Long questionId,
                                           @JsonProperty("createdDate") LocalDateTime createdDate,
                                           @JsonProperty("modifiedDate") LocalDateTime modifiedDate,
                                           @JsonProperty("text") String text, @JsonProperty("owner") AccountResponseDto owner) {
            this(id, questionId, createdDate, modifiedDate, text);
            this.owner = owner;
        }
    

    Assuming AccountResponseDto has its own means to facilitate Jackson's deserialization efforts (e.g. a no-args + setters), it should work fine

    Alternatively, you can register the ParameterNamesModule like so (or opt for some bean definition in case you use frameworks like Spring)

    new ObjectMapper().registerModule(new ParameterNamesModule())
    /*
    you probably want to chain registerModule(new JavaTimeModule()) too 
    since you are using LocalDateTime
    */
    

    When you do so, you no longer have to manually map parameters with eponymous class fields avoiding the clutter

    Note Jackson 3.x supports this module by default, but since you encounter that problem, you probably use Jackson 2.x so explicit module registration is required