Я использую Java Spring Boot для своего проекта, и у меня есть следующий контроллер:
@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);
}
}
И SubjectCreationDTO:
@AllArgsConstructor
@Getter
@Setter
public class SubjectCreationDTO {
private String title;
}
Итак, я получаю эту ошибку при выполнении запроса POST:
Ошибка анализа JSON: невозможно создать экземпляр
pweb.examhelper.dto.subject.SubjectCreationDTO(хотя по крайней мере существует один Создатель): невозможно десериализовать из значения объекта (нет Создатель на основе делегата или свойства)"
Я могу решить эту ошибку, добавив @NoArgsConstructor в SubjectCreationDTO, но зачем это нужно, когда в других случаях у меня почти такой же случай.
@PostMapping
public ResponseEntity<StudentDTO> createStudent(@RequestBody StudentCreationDTO studentCreationDTO) {
StudentDTO savedStudent = studentService.createStudent(studentCreationDTO);
return new ResponseEntity<>(savedStudent, HttpStatus.CREATED);
}
а это класс StudentCreationDTO:
@AllArgsConstructor
@Getter
@Setter
public class StudentCreationDTO {
private String username;
private String firstName;
private String lastName;
private String email;
}
Я понял, что в случае наличия более одного поля вам не нужно указывать @NoArgsConstructor, и библиотека Джексона может так же хорошо анализировать входной JSON из тела. Мой вопрос: почему он имеет такое поведение и почему он не может анализировать, если у меня есть только одно поле в классе без конструктора по умолчанию, но может, если у меня несколько полей?




Чтобы Джексон мог десериализовать Json, ему нужен либо конструктор по умолчанию, либо метод, помеченный @JsonCreator. Без любого из этих двух методов Джексон не может создать экземпляр и вызывает InvalidDefinitionException. Это то, что пытается сказать ошибка, которая не может десериализовать из значения объекта (никакой создатель на основе делегата или свойства).
Используя конструктор по умолчанию, Джексон сначала создает экземпляр класса по умолчанию, а затем внедряет свойства объекта в каждое поле, считанное из Json.
Аналогично, при использовании подхода @JsonCreator Джексон сначала создает экземпляр объекта только со свойствами, указанными в качестве параметров метода, аннотированного @JsonCreator. Затем устанавливает все оставшиеся поля из Json в объект. Аннотированный метод может быть либо параметризованным конструктором, либо статическим методом.
Обычно вы не сможете десериализовать объект с помощью всего лишь @AllArgsContructor, но должна быть какая-то другая конфигурация, которая обрабатывает параметризованное создание экземпляра за вас. Вот также статья из Baeldung, где в пункте 10.1 показан типичный случай, когда класс не десериализуется из-за отсутствия конструктора по умолчанию или метода, помеченного @JsonCreator.
Я также приложил пример, который вы можете попробовать в oneCompiler, где он показывает, как ведет себя Джексон, когда есть только параметризованный конструктор и нет конструктора по умолчанию или @JsonCreator метода. В частности, пример обрабатывает следующие сценарии:
StudentCreationDTO1 не десериализуется, поскольку он предоставляет только @AllArgsConstructor и не имеет конструктора по умолчанию. На самом деле выбрасывается InvalidDefinitionException.
StudentCreationDTO2 десериализуется, поскольку предоставляет конструктор по умолчанию.
StudentCreationDTO3 десериализуется, поскольку предоставляет метод (конструктор), помеченный @JsonCreator. Аннотация не обязательно должна включать все поля класса, а только некоторые из них, просто чтобы Джексон мог создать экземпляр StudentCreationDTO3, а затем установить остальные поля.
public class Main {
public static void main(String[] args) throws JsonProcessingException {
String json = "{\n" +
"\t\"username\": \"johndoe\",\n" +
"\t\"firstName\": \"john\",\n" +
"\t\"lastName\": \"doe\",\n" +
"\t\"email\": \"[email protected]\"\n" +
"}";
ObjectMapper objectMapper = new ObjectMapper();
//Deserializing with no default constructor
try {
StudentCreationDTO1 studentCreationDTO1 = objectMapper.readValue(json, StudentCreationDTO1.class);
System.out.println(studentCreationDTO1);
} catch (InvalidDefinitionException e) {
System.out.println("Throwing InvalidDefinitionException because there is no default constructor or method marked with @JsonCreator");
}
//Deserializing with default constructor
try {
StudentCreationDTO2 studentCreationDTO2 = objectMapper.readValue(json, StudentCreationDTO2.class);
System.out.println("\n" + studentCreationDTO2);
} catch (InvalidDefinitionException e) {
System.out.println("Throwing InvalidDefinitionException because there is no default constructor or method marked with @JsonCreator");
}
//Deserializing with no default constructor but with method annotated with @JsonCreator
try {
StudentCreationDTO3 studentCreationDTO3 = objectMapper.readValue(json, StudentCreationDTO3.class);
System.out.println("\n" + studentCreationDTO3);
} catch (InvalidDefinitionException e) {
System.out.println("Throwing InvalidDefinitionException because there is no default constructor or method marked with @JsonCreator");
}
}
}
Это всего лишь дополнительный раздел, в котором подробно описывается, как работает процесс десериализации, и показано, почему Джексону нужен первый экземпляр для чтения и установки значений из Json. Я также привожу ссылку на отличную статью из Baeldung, в которой рассматриваются все следующие случаи как сериализации, так и десериализации.
public class MyBean {
private String name;
//... default constructor ....
//... standard getName() ...
//Jackson uses the corresponding setter to set the property name
public void setName(String name) {
this.name = name;
}
}
@JsonSetter и именем свойства Json, то Джексон предполагает, что аннотированный метод — это правильный способ установить свойство json.public class MyBean {
private String name;
//... default constructor ....
//... standard getName() ...
//Marking the following method with @JsonSetter because
//The json contains a property called name (value = "name"),
//but Jackson can't find any setter method in the form setName()
@JsonSetter(value = "name")
public void setTheName(String name) {
this.name = name;
}
}
@JsonSetter не указан, но класс содержит только методы получения, Джексон возвращается к отражению, чтобы установить свойства объекта.public class MyBean {
private String name;
//... default constructor ....
//Jackson cannot set a value with just a getter,
//so it falls falls back to reflection to set name
public void getName() {
return name;
}
}
ObjectMapper настроен с помощью setVisibility() метода.ObjectMapper mapper = new ObjectMapper();
mapper.setVisibility(PropertyAccessor.FIELD, Visibility.ANY);
...
public class MyBean {
//Every field is set by Jackson with Visibility.ANY
public String name;
proteced int id;
float value;
private boolean flag;
//... default constructor ....
//... No getters or setters ....
}
обновлена видимость проекта для публики на сайте oneCompiler