杰克逊什么时候需要无参数构造函数进行反序列化?
When does Jackson require no-arg constructor for deserialization?
在我的 spring 启动项目中,我注意到 Jackson 有一个奇怪的行为。我在互联网上搜索,找到了该怎么做,但还没有找到 为什么。
UserDto:
@Setter
@Getter
@AllArgsConstructor
public class UserDto {
private String username;
private String email;
private String password;
private String name;
private String surname;
private UserStatus status;
private byte[] avatar;
private ZonedDateTime created_at;
}
添加新用户工作正常。
TagDto:
@Setter
@Getter
@AllArgsConstructor
public class TagDto {
private String tag;
}
尝试添加新标签时出现错误:
com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot construct instance of TagDto (although at least one Creator exists): cannot deserialize from Object value (no delegate- or property-based Creator)
问题的解决方案是将零参数构造函数添加到 TagDto class。
为什么 Jackson 在 TagDto 中需要无参数构造函数进行反序列化,而在 UserDto 中工作得很好?
使用相同的方法添加两者。
我的 Tag 和 User 实体都用
注释
@Entity
@Setter
@Getter
@NoArgsConstructor
并拥有所有 args 构造函数:
@Entity
@Setter
@Getter
@NoArgsConstructor
public class User extends AbstractModel {
private String username;
private String password;
private String email;
private String name;
private String surname;
private UserStatus status;
@Lob
private byte[] avatar;
@Setter(AccessLevel.NONE)
private ZonedDateTime created_at;
public User(final String username, final String password, final String email, final String name, final String surname) {
this.username = username;
this.password = password;
this.email = email;
this.name = name;
this.surname = surname;
this.created_at = ZonedDateTime.now();
}
}
@Entity
@Setter
@Getter
@NoArgsConstructor
@AllArgsConstructor
public class Tag extends AbstractModel {
private String tag;
}
@MappedSuperclass
@Getter
public abstract class AbstractModel {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
}
实体生成:
@PostMapping(path = "/add")
public ResponseEntity<String> add(@Valid @RequestBody final D dto) {
this.abstractModelService.add(dto);
return new ResponseEntity<>("Success", HttpStatus.CREATED);
}
public void add(final D dto) {
//CRUD repository save method
this.modelRepositoryInterface.save(this.getModelFromDto(dto));
}
@Override
protected Tag getModelFromDto(final TagDto tagDto) {
return new Tag(tagDto.getTag());
}
@Override
protected User getModelFromDto(final UserDto userDto) {
return new User(userDto.getUsername(), userDto.getPassword(), userDto.getEmail(), userDto.getName(), userDto.getSurname());
}
解析时出现错误JSON
{"tag":"example"}
通过邮递员发送 localhost:8081/tag/add, returns
{
"timestamp": "2020-09-26T18:50:39.974+00:00",
"status": 400,
"error": "Bad Request",
"message": "",
"path": "/tag/add"
}
我正在使用 Lombok v1.18.12 和 Spring boot 2.3.3.RELEASE 以及 Jackson v2.11.2.
TL;DR: 解决方案在最后。
Jackson 支持多种创建 POJO 的方式。下面列出了最常见的方法,但可能不是完整列表:
使用 no-arg 构造函数创建实例,然后调用 setter 方法分配 属性 值。
public class Foo {
private int id;
public int getId() { return this.id; }
@JsonProperty
public void setId(int id) { this.id = id; }
}
指定 @JsonProperty
is optional, but can be used to fine-tune the mappings, together with annotations like @JsonIgnore
, @JsonAnyGetter
, ...
使用带参数的构造函数创建实例。
public class Foo {
private int id;
@JsonCreator
public Foo(@JsonProperty("id") int id) {
this.id = id;
}
public int getId() {
return this.id;
}
}
为构造函数指定 @JsonCreator
是可选的,但我认为如果有多个构造函数,则这是必需的。为参数指定 @JsonProperty
是可选的,但如果参数名称未包含在 class 文件中(-parameters
编译器选项),则需要为属性命名。
参数暗示属性是必需的。可以使用 setter 方法设置可选属性。
使用工厂方法创建实例。
public class Foo {
private int id;
@JsonCreator
public static Foo create(@JsonProperty("id") int id) {
return new Foo(id);
}
private Foo(int id) {
this.id = id;
}
public int getId() {
return this.id;
}
}
使用 String
构造函数从文本值创建实例。
public class Foo {
private int id;
@JsonCreator
public Foo(String str) {
this.id = Integer.parseInt(id);
}
public int getId() {
return this.id;
}
@JsonValue
public String asJsonValue() {
return Integer.toString(this.id);
}
}
这在 POJO 具有简单的文本表示时很有用,例如LocalDate
是具有 3 个属性(year
、month
、dayOfMonth
)的 POJO,但通常最好序列化为单个字符串(yyyy-MM-dd
格式)。 @JsonValue
标识在序列化期间使用的方法,@JsonCreator
标识在反序列化期间使用的 constructor/factory-method。
注意: 这也可以用于 single-value 使用 JSON 值而不是 String
的构造,但这种情况非常罕见。
好的,这就是背景信息。问题中的示例发生了什么,UserDto
起作用是因为只有一个构造函数(因此不需要 @JsonCreator
)和许多参数(因此不需要 @JsonProperty
) .
但是,对于 TagDto
,只有一个 single-argument 构造函数,没有任何注释,因此 Jackson class 将该构造函数确定为一个类型#4(来自我上面的列表),不是类型#2。
这意味着它期望 POJO 是 value-class,其中封闭对象的 JSON 将是 { ..., "tag": "value", ... }
,而不是 { ..., "tag": {"tag": "example"}, ... }
。
要解决此问题,您需要通过指定 [=15] 告诉 Jackson 构造函数是 属性 初始化构造函数 (#2),而不是 value-type 构造函数 (#4) =] 在构造函数参数上。
这意味着您不能让 Lombok 为您创建构造函数:
@Setter
@Getter
public class TagDto {
private String tag;
public TagDto(@JsonProperty("tag") String tag) {
this.tag = tag;
}
}
在我的 spring 启动项目中,我注意到 Jackson 有一个奇怪的行为。我在互联网上搜索,找到了该怎么做,但还没有找到 为什么。
UserDto:
@Setter
@Getter
@AllArgsConstructor
public class UserDto {
private String username;
private String email;
private String password;
private String name;
private String surname;
private UserStatus status;
private byte[] avatar;
private ZonedDateTime created_at;
}
添加新用户工作正常。
TagDto:
@Setter
@Getter
@AllArgsConstructor
public class TagDto {
private String tag;
}
尝试添加新标签时出现错误:
com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot construct instance of TagDto (although at least one Creator exists): cannot deserialize from Object value (no delegate- or property-based Creator)
问题的解决方案是将零参数构造函数添加到 TagDto class。
为什么 Jackson 在 TagDto 中需要无参数构造函数进行反序列化,而在 UserDto 中工作得很好?
使用相同的方法添加两者。 我的 Tag 和 User 实体都用
注释@Entity
@Setter
@Getter
@NoArgsConstructor
并拥有所有 args 构造函数:
@Entity
@Setter
@Getter
@NoArgsConstructor
public class User extends AbstractModel {
private String username;
private String password;
private String email;
private String name;
private String surname;
private UserStatus status;
@Lob
private byte[] avatar;
@Setter(AccessLevel.NONE)
private ZonedDateTime created_at;
public User(final String username, final String password, final String email, final String name, final String surname) {
this.username = username;
this.password = password;
this.email = email;
this.name = name;
this.surname = surname;
this.created_at = ZonedDateTime.now();
}
}
@Entity
@Setter
@Getter
@NoArgsConstructor
@AllArgsConstructor
public class Tag extends AbstractModel {
private String tag;
}
@MappedSuperclass
@Getter
public abstract class AbstractModel {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
}
实体生成:
@PostMapping(path = "/add")
public ResponseEntity<String> add(@Valid @RequestBody final D dto) {
this.abstractModelService.add(dto);
return new ResponseEntity<>("Success", HttpStatus.CREATED);
}
public void add(final D dto) {
//CRUD repository save method
this.modelRepositoryInterface.save(this.getModelFromDto(dto));
}
@Override
protected Tag getModelFromDto(final TagDto tagDto) {
return new Tag(tagDto.getTag());
}
@Override
protected User getModelFromDto(final UserDto userDto) {
return new User(userDto.getUsername(), userDto.getPassword(), userDto.getEmail(), userDto.getName(), userDto.getSurname());
}
解析时出现错误JSON
{"tag":"example"}
通过邮递员发送 localhost:8081/tag/add, returns
{
"timestamp": "2020-09-26T18:50:39.974+00:00",
"status": 400,
"error": "Bad Request",
"message": "",
"path": "/tag/add"
}
我正在使用 Lombok v1.18.12 和 Spring boot 2.3.3.RELEASE 以及 Jackson v2.11.2.
TL;DR: 解决方案在最后。
Jackson 支持多种创建 POJO 的方式。下面列出了最常见的方法,但可能不是完整列表:
使用 no-arg 构造函数创建实例,然后调用 setter 方法分配 属性 值。
public class Foo { private int id; public int getId() { return this.id; } @JsonProperty public void setId(int id) { this.id = id; } }
指定
@JsonProperty
is optional, but can be used to fine-tune the mappings, together with annotations like@JsonIgnore
,@JsonAnyGetter
, ...使用带参数的构造函数创建实例。
public class Foo { private int id; @JsonCreator public Foo(@JsonProperty("id") int id) { this.id = id; } public int getId() { return this.id; } }
为构造函数指定
@JsonCreator
是可选的,但我认为如果有多个构造函数,则这是必需的。为参数指定@JsonProperty
是可选的,但如果参数名称未包含在 class 文件中(-parameters
编译器选项),则需要为属性命名。参数暗示属性是必需的。可以使用 setter 方法设置可选属性。
使用工厂方法创建实例。
public class Foo { private int id; @JsonCreator public static Foo create(@JsonProperty("id") int id) { return new Foo(id); } private Foo(int id) { this.id = id; } public int getId() { return this.id; } }
使用
String
构造函数从文本值创建实例。public class Foo { private int id; @JsonCreator public Foo(String str) { this.id = Integer.parseInt(id); } public int getId() { return this.id; } @JsonValue public String asJsonValue() { return Integer.toString(this.id); } }
这在 POJO 具有简单的文本表示时很有用,例如
LocalDate
是具有 3 个属性(year
、month
、dayOfMonth
)的 POJO,但通常最好序列化为单个字符串(yyyy-MM-dd
格式)。@JsonValue
标识在序列化期间使用的方法,@JsonCreator
标识在反序列化期间使用的 constructor/factory-method。注意: 这也可以用于 single-value 使用 JSON 值而不是
String
的构造,但这种情况非常罕见。
好的,这就是背景信息。问题中的示例发生了什么,UserDto
起作用是因为只有一个构造函数(因此不需要 @JsonCreator
)和许多参数(因此不需要 @JsonProperty
) .
但是,对于 TagDto
,只有一个 single-argument 构造函数,没有任何注释,因此 Jackson class 将该构造函数确定为一个类型#4(来自我上面的列表),不是类型#2。
这意味着它期望 POJO 是 value-class,其中封闭对象的 JSON 将是 { ..., "tag": "value", ... }
,而不是 { ..., "tag": {"tag": "example"}, ... }
。
要解决此问题,您需要通过指定 [=15] 告诉 Jackson 构造函数是 属性 初始化构造函数 (#2),而不是 value-type 构造函数 (#4) =] 在构造函数参数上。
这意味着您不能让 Lombok 为您创建构造函数:
@Setter
@Getter
public class TagDto {
private String tag;
public TagDto(@JsonProperty("tag") String tag) {
this.tag = tag;
}
}