I am using Java Spring Boot for my project and I have the following controller:
@AllArgsConstructor
@RestController
@RequestMapping("/api/subject")
public class SubjectController {
private SubjectService subjectService;
@PostMapping
public void createSubject(@RequestBody SubjectCreationDTO subjectCreationDTO) {
LoggingController.getLogger().info(subjectCreationDTO.getTitle());
// subjectService.createSubject(subjectCreationDTO);
}
}
And SubjectCreationDTO:
@AllArgsConstructor
@Getter
@Setter
public class SubjectCreationDTO {
private String title;
}
So I get this error when making a POST request:
JSON parse error: Cannot construct instance of
pweb.examhelper.dto.subject.SubjectCreationDTO(although at least one Creator exists): cannot deserialize from Object value (no delegate- or property-based Creator)"
I can solve this error by adding @NoArgsConstructor to SubjectCreationDTO, but why is this necessary, when in other cases, I have the almost exactly the same case.
@PostMapping
public ResponseEntity<StudentDTO> createStudent(@RequestBody StudentCreationDTO studentCreationDTO) {
StudentDTO savedStudent = studentService.createStudent(studentCreationDTO);
return new ResponseEntity<>(savedStudent, HttpStatus.CREATED);
}
and this is the StudentCreationDTO class:
@AllArgsConstructor
@Getter
@Setter
public class StudentCreationDTO {
private String username;
private String firstName;
private String lastName;
private String email;
}
I have figured it out that in case of having more than just one field, you do not have to specify @NoArgsConstructor and Jackson Library can parse the input JSON from the body just as fine. My question is why it has this behavior, and why it can't parse if I have only one field in the class without the default constructor, but it can if I have multiple fields?
In order for Jackson to deserialize a Json, it either needs a default constructor or a method annotated with
@JsonCreator. Without any of these two methods, Jackson is not able to instantiate an instance and raises anInvalidDefinitionException. This is what the error cannot deserialize from Object value (no delegate- or property-based Creator) is trying to say.With a default constructor, Jackson first creates a default instance of the class and then injects the object's properties with each field read from the Json.
Likewise, with the
@JsonCreatorapproach, Jackson first instantiates an object with only the properties specified as the parameters of the method annotated with@JsonCreator. Then, sets each remaining field from the Json into the object. The annotated method can be either a parameterized constructor or a static method.Normally, you shouldn't be able to deserialize an object with just an
@AllArgsContructor, but there must be some other configuration that handles a parameterized instantiation for you. Here is also an article from Baeldung where at point 10.1 shows a typical case of a class not being deserialized because it lacks of both a default constructor or a method annotated with@JsonCreator.I've also attached an example that you can try at oneCompiler where it shows how Jackson behaves when there is only a parameterized constructor and no default constructor or
@JsonCreatormethod. Precisely, the example handles the following scenarios:StudentCreationDTO1 is not deserialized as it provides only an @AllArgsConstructor and no default constructor. In fact, an
InvalidDefinitionExceptionis thrown.StudentCreationDTO2 is deserialized as it provides a default constructor.
StudentCreationDTO3 is deserialized as it provides a (constructor) method annotated with
@JsonCreator. The annotation doesn't need to include all the class' fields, but only a few of them, just so that Jackson is able to create an instance ofStudentCreationDTO3and then set the remaining fields.Further Notes on Jackson Deserializtion
This is just an extra section that furthers how the deserialization process works, and shows why Jackson needs a first instance in order to read and set values from a Json. I'm also linking a great article from Baeldung that addresses all the following cases for both serialization and deserialization.
@JsonSetterand the name of the Json property, then Jackson assumes that the annotated method is the right way to set the json property.@JsonSettermethod is provided, but the class exhibits only getter methods, then Jackson falls back on reflection to set the object's properties.ObjectMapperis configured with thesetVisibility()method.