为什么 Jackson 需要默认构造函数?

问题描述 投票:0回答:1

我正在为我的项目使用 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。我的问题是为什么它有这种行为,为什么如果我在类中只有一个字段没有默认构造函数,它就无法解析,但如果我有多个字段,它就可以解析?

java spring spring-mvc controller jackson
1个回答
0
投票

为了让 Jackson 反序列化 Json,它需要一个默认构造函数或一个用

@JsonCreator
注释的方法。如果没有这两种方法中的任何一种,Jackson 就无法实例化实例并引发
InvalidDefinitionException

使用默认构造函数,Jackson 首先创建类的默认实例,然后将对象的属性注入到从 Json 读取的每个字段中。

同样,在

@JsonCreator
方法中,Jackson 首先实例化一个对象,该对象仅具有指定为用
@JsonCreator
注释的方法的参数的属性。然后,将 Json 中的每个剩余字段设置到对象中。带注释的方法可以是参数化构造函数,也可以是静态方法。

我不知道如何仅使用

@AllArgsContructor
反序列化对象,但必须有其他一些配置来为您处理参数化实例化。这里还有一篇来自 Baeldung 的文章,其中第 10.1 点显示了类未反序列化的典型情况,因为它缺少默认构造函数或用
@JsonCreator
注释的方法。

我还附上了一个示例,您可以在 oneCompiler 中尝试,其中显示了当只有参数化构造函数时 Jackson 的行为方式。准确地说,该示例处理以下场景:

  • 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");
        }
    }
}

Jackson 反序列化的进一步说明

这只是一个额外的部分,它进一步介绍了反序列化过程的工作原理,并说明了为什么 Jackson 需要第一个实例才能从 Json 读取和设置值。我还链接了来自 Baeldung 的一篇精彩文章,该文章解决了序列化和反序列化的以下所有情况。

  • 如果属性有 setter,那么它的值是通过相应的 setter 方法设置的。
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;
    }
}
  • 如果没有提供 setter,但该类展示了一个用
    @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;
    }
}
  • 如果没有提供 setter 或
    @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;
    }
}
  • 如果类不提供任何 getter 或 setter,默认情况下,Jackson 仅设置公共字段,否则当使用
    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 ....
}
© www.soinside.com 2019 - 2024. All rights reserved.