我正在为我的项目使用 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,Jackson 库也可以解析正文中的输入 JSON。我的问题是为什么它有这种行为,为什么如果我在类中只有一个字段没有默认构造函数,它就无法解析,但如果我有多个字段,它就可以解析?
为了让 Jackson 反序列化 Json,它需要一个默认构造函数或一个用
@JsonCreator
注释的方法。如果没有这两种方法中的任何一种,杰克逊就会提出 InvalidDefinitionException
。
使用默认构造函数,Jackson 首先创建类的实例,然后将对象的属性注入到从 Json 读取的每个字段中。
同样,在
@JsonCreator
方法中,Jackson 首先实例化一个对象,该对象仅具有指定为用 @JsonCreator
注释的方法的参数的属性。然后,将 json 中剩余的每个属性注入到对象中。
我不知道您如何仅使用具有所有参数的构造函数(@AllArgsConstructor)来反序列化对象,必须有一些其他配置使您在这两种情况之一中回退。这是一个您可以在 oneCompiler 尝试的示例,其中:
StudentCreationDTO1 未反序列化,因为它仅提供 @AllArgsConstructor 而没有默认构造函数。事实上,
InvalidDefinitionException
被抛出了。
StudentCreationDTO2 被反序列化,因为它提供了默认构造函数。
StudentCreationDTO3 被反序列化,因为它提供了一个用
@JsonCreator
注释的方法。该注释甚至不包含类的所有字段,而仅包含其中的几个字段,以便 Jackson 能够创建 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");
}
}
}
在这两种情况下,使用默认构造函数和用
@JsonCreator
注解的方法,属性注入以不同的方式执行:
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 属性的名称,则 Jackson 认为带注释的方法是设置 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
方法,但该类仅展示 getter 方法,则 Jackson 会依靠反射来设置对象的属性。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 ....
}