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
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