[英]When does Jackson require no-arg constructor for deserialization?
In my spring boot project, I noticed a strange Jackson behavior.在我的 Spring Boot 项目中,我注意到了一个奇怪的 Jackson 行为。 I searched over internet, found out what to do, but haven't found out why .我在互联网上搜索,找到了该怎么做,但还没有找到原因。
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;
}
Adding a new user works just fine.添加新用户工作正常。
TagDto:标签:
@Setter
@Getter
@AllArgsConstructor
public class TagDto {
private String tag;
}
Trying to add a new tag ends with an error:尝试添加新标签以错误结束:
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) com.fasterxml.jackson.databind.exc.MismatchedInputException:无法构造 TagDto 的实例(尽管至少存在一个 Creator):无法从 Object 值反序列化(没有基于委托或属性的 Creator)
The solution to the problem was to add zero-arg constructor to the TagDto class.该问题的解决方案是在 TagDto 类中添加零参数构造函数。
Why does Jackson require no-arg constructor for deserialization in TagDto, while working just fine with UserDto?为什么 Jackson 在 TagDto 中需要无参数构造函数进行反序列化,而与 UserDto 一起工作得很好?
Used same method for adding both.使用相同的方法添加两者。 My Tag and User entities are both annotated with我的标签和用户实体都用
@Entity
@Setter
@Getter
@NoArgsConstructor
and have all args constructors:并拥有所有 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;
}
Entity generation:实体生成:
@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());
}
Error occurs when parsing JSON解析JSON时出错
{"tag":"example"}
sent via postman localhost:8081/tag/add, returns通过邮递员发送 localhost:8081/tag/add,返回
{
"timestamp": "2020-09-26T18:50:39.974+00:00",
"status": 400,
"error": "Bad Request",
"message": "",
"path": "/tag/add"
}
I am using Lombok v1.18.12 and Spring boot 2.3.3.RELEASE with Jackson v2.11.2.我正在使用 Lombok v1.18.12 和 Spring boot 2.3.3.RELEASE 和 Jackson v2.11.2。
TL;DR: Solution is at the end. TL;DR:解决方案在最后。
Jackson supports multiple ways of creating POJOs. Jackson 支持多种创建 POJO 的方式。 The following lists the most common ways, but it likely not a complete list:下面列出了最常见的方法,但可能不是完整的列表:
Create instance using no-arg constructor, then call setter methods to assign property values.使用无参数构造函数创建实例,然后调用 setter 方法来分配属性值。
public class Foo { private int id; public int getId() { return this.id; } @JsonProperty public void setId(int id) { this.id = id; } }
Specifying @JsonProperty
is optional, but can be used to fine-tune the mappings, together with annotations like @JsonIgnore
, @JsonAnyGetter
, ...指定@JsonProperty
是可选的,但可用于微调映射,以及@JsonIgnore
、 @JsonAnyGetter
等注释。
Create instance using constructor with arguments.使用带参数的构造函数创建实例。
public class Foo { private int id; @JsonCreator public Foo(@JsonProperty("id") int id) { this.id = id; } public int getId() { return this.id; } }
Specifying @JsonCreator
for the constructor is optional, but I believe it is required if there is more than one constructor.为构造函数指定@JsonCreator
是可选的,但我相信如果有多个构造函数,则这是必需的。 Specifying @JsonProperty
for the parameters is optional, but is required for naming the properties if the parameter names are not included in the class file ( -parameters
compiler option).为参数指定@JsonProperty
是可选的,但如果类文件中不包含参数名称( -parameters
编译器选项),则命名属性时需要使用@JsonProperty
。
The parameters imply that the properties are required.参数暗示属性是必需的。 Optional properties can be set using setter methods.可以使用 setter 方法设置可选属性。
Create instance using factory method.使用工厂方法创建实例。
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; } }
Create instance from text value using String
constructor.使用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); } }
This is useful when a the POJO has a simply text representation, eg a LocalDate
is a POJO with 3 properties ( year
, month
, dayOfMonth
), but is generally best serialized as a single string ( yyyy-MM-dd
format).当 POJO 具有简单的文本表示时,这很有用,例如LocalDate
是具有 3 个属性( year
、 month
、 dayOfMonth
)的 POJO,但通常最好将其序列化为单个字符串( yyyy-MM-dd
格式)。 @JsonValue
identifies the method to be used during serialization, and @JsonCreator
identifies the constructor/factory-method to be used during deserialization. @JsonValue
标识序列化过程中使用的方法,并且@JsonCreator
识别反序列化过程中使用的构造/工厂方法。
Note: This can also be used for single-value construction using JSON values other than String
, but that is very rare.注意:这也可以用于使用String
以外的 JSON 值的单值构造,但这种情况非常少见。
Ok, that was the background information.好的,这就是背景信息。 What is happening for the examples in the question, it that UserDto
works because there is only one constructor (so @JsonCreator
is not needed), and many arguments (so @JsonProperty
is not needed).问题中的示例发生了什么, UserDto
起作用,是因为只有一个构造函数(因此@JsonCreator
)和许多参数(因此@JsonProperty
)。
However, for TagDto
there is only a single-argument constructor without any annotations, so Jackson classifies that constructor as a type #4 (from my list above), not a type #2.但是,对于TagDto
,只有一个没有任何注释的单参数构造函数,因此 Jackson 将该构造函数分类为类型 #4(来自我上面的列表),而不是类型 #2。
Which means that it is expecting the POJO to be a value-class, where the JSON for the enclosing object would be { ..., "tag": "value", ... }
, not { ..., "tag": {"tag": "example"}, ... }
.这意味着它期望 POJO 是一个值类,其中封闭对象的 JSON 将是{ ..., "tag": "value", ... }
,而不是{ ..., "tag": {"tag": "example"}, ... }
。
To resolve the issue, you need to tell Jackson that the constructor is a property initializing constructor (#2), not a value-type constructor (#4), by specifying @JsonProperty
on the constructor argument.要解决此问题,您需要通过在构造函数参数上指定@JsonProperty
来告诉 Jackson 构造函数是属性初始化构造函数(#2),而不是值类型构造函数(#4)。
This means that you cannot have Lombok create the constructor for you:这意味着您不能让 Lombok 为您创建构造函数:
@Setter
@Getter
public class TagDto {
private String tag;
public TagDto(@JsonProperty("tag") String tag) {
this.tag = tag;
}
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.