当前位置: 首页 > 知识库问答 >
问题:

杰克逊何时需要无参数构造函数进行反序列化?

司宏伯
2023-03-14

在我的Spring开机项目中,我注意到了杰克逊奇怪的行为。我在网上搜索了一下,找到了该做什么,但还没找到原因。

用户收件人:

@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:无法构造TagDto的实例(尽管至少存在一个创建者):无法从对象值反序列化(没有基于委托或属性的创建者)

该问题的解决方案是向TagDto类添加零参数构造函数。

为什么Jackson在TagDto中进行反序列化时不需要arg构造函数,而在UserDto中工作得很好?

使用相同的方法添加两者。我的标签和用户实体都用

@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,返回

{
    "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。Jackson v2.11.2的发布。

共有1个答案

翟鹏
2023-03-14

TL;DR:解决方案已经结束。

Jackson支持多种创建Pojo的方法。以下列出了最常见的方法,但可能不是一个完整的列表:

>

  • 不使用参数构造函数创建实例,然后调用setter方法来分配属性值。

    public class Foo {
        private int id;
    
        public int getId() { return this.id; }
    
        @JsonProperty
        public void setId(int id) { this.id = id; }
    }
    

    指定JsonProperty是可选的,但可以用于微调映射,以及诸如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是可选的,但如果类文件中不包括参数名,则需要指定属性(-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具有简单的文本表示时,这很有用,例如,局部日期是具有3个属性(年、月、月)的POJO,但通常最好序列化为单个字符串(yyyy-MM-dd格式)@JsonValue标识序列化期间要使用的方法,而@JsonCreator标识反序列化期间要使用的构造函数/工厂方法。

    注意:这也可以用于使用JSON值而不是字符串的单值构造,但这种情况非常罕见。

    好的,这就是背景信息。对于问题中的示例来说,UserDto之所以有效,是因为只有一个构造函数(因此不需要JsonCreator)和许多参数(因此不需要JsonProperty)。

    但是,对于TagDto,只有一个没有任何注释的单参数构造函数,因此Jackson将该构造函数分类为类型#4(来自我上面的列表),而不是类型#2。

    这意味着它期望POJO是一个值类,其中封闭对象的JSON将是{...,"tag":"value",...},而不是{...,"tag":{"tag":"example"},...}

    为了解决这个问题,您需要通过在构造函数参数上指定@JsonProperty来告诉Jackson构造函数是属性初始化构造函数(#2),而不是值类型构造函数(#4)。

    这意味着您不能让Lombok为您创建构造函数:

    @Setter
    @Getter
    public class TagDto {
    
        private String tag;
    
        public TagDto(@JsonProperty("tag") String tag) {
            this.tag = tag;
        }
    }
    

  •  类似资料:
    • 我在我的项目中使用FasterXML/Jackson Databind已经有一段时间了,一切都很顺利,直到我发现这篇文章并开始使用这种方法对没有@JsonProperty注释的对象进行序列化。 问题是,当我有一个接受多个参数并使用@JsonCreator注释装饰这个构造函数时,Jackson会抛出以下错误: 我已经创建了一个小项目来说明这个问题,我试图对这个类进行序列化: 反序列化代码如下: 我已

    • 问题内容: 另一个问题,但与此相关:使用Jackson来 反序列化JSON-为什么JsonMappingException“没有合适的构造函数”? 这次我遇到了另一个错误,即Jackson解串器抱怨类ProtocolContainer中没有“单字符串构造器/工厂方法”。 但是,如果我添加一个单字符串构造函数,如下所示: 异常确实消失了,但是我希望存在的所有异常都为“空”,即其所有属性都处于其初始状

    • 问题内容: 我正在尝试使用杰克逊对物体进行去电 我有这个例外: 我知道这正在发生,因为这是我的构造函数: 因此,我的构造函数接收到HttpResponse参数,但我没有传递它,但我不知道该怎么做。我不能用一个空的构造函数过度计费,因为我需要以这种方式接收HttpResponse对象。当我调用readValue()方法时,有什么方法可以传递此构造函数参数?或者在这种情况下最好的选择是什么?我感谢您的

    • 问题内容: 我正在使用Jackson示例代码对POJO进行反序列化: 这行抛出一个NoSuchMethodError: 我不明白 问题答案: 我猜您的Jackson JAR不同步。本类是JAR,和类是在。 确保它们都是相同的版本。

    • 我在spring-boot项目中使用Jackson和hibernate: 并将其添加到转换器中: 我使用facebook网络钩子在Messenger中重新获取关于新消息的信息: 有效负载参数是一个字符串,但jackson转换器无法对其进行反序列化: W.S.M.S.DefaultHandlerExceptionResolver:未能读取HTTP消息:org.springframework.HTTP

    • 问题内容: 我有一个杰克逊问题。 有没有一种方法可以反序列化可能具有两种类型的属性,对于某些对象,它看起来像这样 然后对于其他人则显示为空数组,即 任何帮助表示赞赏! 谢谢! 问题答案: Jackson目前没有内置配置来自动处理这种特殊情况,因此必须进行自定义反序列化处理。 以下是这种自定义反序列化的外观示例。 (您可以使用DeserializationConfig.Feature.ACCEPT_