Jackson中的ConstructorDetector指南

使用Jackson的一个重要方面是了解它如何将JSON数据映射到 Java 对象,这通常涉及使用构造函数。此外,ConstructorDetector是 Jackson 中的一个关键组件,它影响在反序列化过程中如何选择构造函数。

在本教程中,我们将详细探讨ConstructorDetector ,解释其目的、配置和用法。

 ConstructorDetector概述
ConstructorDetector是 Jackson 数据绑定模块中的一项功能,可帮助确定在反序列化期间应考虑使用类的哪些构造函数来创建对象。Jackson 使用构造函数来实例化对象并使用 JSON 数据填充其字段。

ConstructorDetector允许我们自定义和控制 Jackson 应该使用哪些构造函数,从而在反序列化过程中提供更多灵活性。

配置ConstructorDetector
Jackson 提供了几个预定义的ConstructorDetector配置,包括USE_PROPERTIES_BASED、USE_DELEGATING、EXPLICIT_ONLY和DEFAULT。

1.基于USE_PROPERTIES_BASED
当我们的类具有与 JSON 属性匹配的构造函数时,此配置很有用。我们来看一个简单的实际示例:

public class User {
    private String firstName;
    private String lastName;
    private int age;
    public User(){
    }
    public User(String firstName, String lastName, int age) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.age = age;
    }
    public String getFirstName() {
        return firstName;
    }
    public String getLastName() {
        return lastName;
    }
    public int getAge() {
        return age;
    }
}

在这个场景中,User类有firstName、lastName和age属性。Jackson 会在User中寻找一个参数与这些属性匹配的构造函数,它会在提供的User(String firstName, String lastName, int age)构造函数中找到它。

现在,当使用 Jackson 的ConstructorDetector.USE_PROPERTIES_BASED将 JSON 反序列化为 Java 对象时,Jackson 将使用与 JSON 对象中的属性最匹配的构造函数:

@Test
public void givenUserJson_whenUsingPropertiesBased_thenCorrect() throws Exception {
    String json = "{\"firstName\": \"John\", \"lastName\": \"Doe\", \"age\": 25}";
    ObjectMapper mapper = JsonMapper.builder()
      .constructorDetector(ConstructorDetector.USE_PROPERTIES_BASED)
      .build();
    User user = mapper.readValue(json, User.class);
    assertEquals("John", user.getFirstName());
    assertEquals(25, user.getAge());
}

这里,名为json的字符串表示具有firstName、lastName和age属性的 JSON 对象,这些属性对应于User类的构造函数参数。使用 mapper.Jackson 反序列化 JSON 时,将使用 readValue(json, User.class) 方法利用 具有与 JSON 属性匹配的参数的构造函数。

如果 JSON 包含类中不存在的其他字段,Jackson 将忽略这些字段而不会引发错误。例如:

String json = "{\"firstName\": \"John\", \"lastName\": \"Doe\", \"age\": 25, \"extraField\": \"extraValue\"}";
User user = mapper.readValue(json, User.class);
在这种情况下,extraField将被忽略。但是,如果构造函数参数与 JSON 属性不完全匹配,Jackson 可能无法找到合适的构造函数并抛出错误。

.2.使用DELEGATING
USE_DELEGATING 配置允许Jackson 将对象创建委托给单参数构造函数。当 JSON 数据结构与单个参数的结构一致时,这会很有用,从而实现简洁的对象创建。

考虑一个用例,其中我们有一个包装单个字符串值的StringWrapper类:

public class StringWrapper {
    private String value;
    @JsonCreator(mode = JsonCreator.Mode.DELEGATING)
    public StringWrapper(@JsonProperty("value") String value) {
        this.value = value;
    }
    @JsonProperty("value")
    public String getValue() {
        return value;
    }
}

StringWrapper类有一个用@JsonCreator和@JsonProperty注释的单参数构造函数,表明 Jackson 应该使用委托来创建对象。

让我们使用 Jackson 和ConstructorDetector.USE_DELEGATING将 JSON 反序列化为 Java 对象:

@Test
public void givenStringJson_whenUsingDelegating_thenCorrect() throws Exception {
    String json = "\"Hello, world!\"";
    ObjectMapper mapper = JsonMapper.builder()
      .constructorDetector(ConstructorDetector.USE_DELEGATING)
      .build();
    StringWrapper wrapper = mapper.readValue(json, StringWrapper.class);
    assertEquals("Hello, world!", wrapper.getValue());
}

在这里,我们使用 Jackson 的ConstructorDetector.USE_DELEGATING将 JSON 字符串值“ Hello, world! ”反序列化为StringWrapper对象。Jackson利用StringWrapper的单参数构造函数,正确映射 JSON 字符串值。

如果 JSON 结构与单参数构造函数不一致,Jackson 将抛出错误。例如:

String json = "{\"value\": \"Hello, world!\", \"extraField\": \"extraValue\"}";
StringWrapper wrapper = mapper.readValue(json, StringWrapper.class);

在这种情况下,附加字段extraField会导致错误,因为构造函数需要单个字符串值,而不是 JSON 对象。

3. EXPLICIT_ONLY
此配置可确保仅使用明确注释的构造函数。此外,它还对构造函数选择提供了严格的控制,允许开发人员指定 Jackson 在反序列化期间应考虑哪些构造函数。

考虑这样一个场景:Product类代表一个具有名称和价格的产品:

public class Product {
    private String value;
    private double price;
    @JsonCreator
    public Product(@JsonProperty("value") String value, @JsonProperty("price") double price) {
        this.value = value;
        this.price = price;
    }
    public String getName() {
        return value;
    }
    public double getPrice() {
        return price;
    }
}

此类有一个用@JsonCreator注释的构造函数,表示 Jackson 应该在反序列化期间使用基于构造函数的显式实例化。

我们来看看反序列化的过程:

@Test
public void givenProductJson_whenUsingExplicitOnly_thenCorrect() throws Exception {
    String json = "{\"value\": \"Laptop\", \"price\": 999.99}";
    ObjectMapper mapper = JsonMapper.builder()
      .constructorDetector(ConstructorDetector.EXPLICIT_ONLY)
      .build();
    Product product = mapper.readValue(json, Product.class);
    assertEquals("Laptop", product.getName());
    assertEquals(999.99, product.getPrice(), 0.001);
}

在此测试方法中,我们利用ConstructorDetector.EXPLICIT_ONLY 配置将表示产品的 JSON 对象反序列化为Product对象。仅考虑Product类的注释构造函数。

如果 JSON 对象具有构造函数中不存在的附加字段或缺少必填字段,Jackson 将抛出错误。例如:

String json = "{\"value\": \"Laptop\"}";
Product product = mapper.readValue(json, Product.class);

由于缺少价格字段,这将导致错误。

String json = "{\"value\": \"Laptop\", \"price\": 999.99, \"extraField\": \"extraValue\"}";
Product product = mapper.readValue(json, Product.class);

这也会由于意外字段extraField而导致错误。

4.DEFAULT
DEFAULT配置通过考虑各种构造函数选择策略来提供一种平衡的方法。此类配置旨在选择符合 JSON 结构的构造函数,同时考虑自定义注释和其他配置选项。

考虑这样一个场景: Address类代表一个邮政地址:

public class Address {
    private String street;
    private String city;
    public Address(){
    }
    public Address(String street, String city) {
        this.street = street;
        this.city = city;
    }
    public String getStreet() {
        return street;
    }
    public String getCity() {
        return city;
    }
}

Address类有一个构造函数,其参数与 JSON 属性street和city匹配。

让我们使用 Jackson 和ConstructorDetector.DEFAULT将 JSON 反序列化为 Java 对象:

@Test
public void givenAddressJson_whenUsingDefault_thenCorrect() throws Exception {
    String json = "{\"street\": \蕋 Main St\", \"city\": \"Springfield\"}";
    ObjectMapper mapper = JsonMapper.builder()
      .constructorDetector(ConstructorDetector.DEFAULT)
      .build();
    Address address = mapper.readValue(json, Address.class);
    assertEquals("123 Main St", address.getStreet());
    assertEquals("Springfield", address.getCity());
}

Jackson 采用其默认启发式方法来选择与 JSON 结构匹配的构造函数,确保从 JSON 数据中准确地实例化对象。

如果 JSON 结构更复杂或包含不直接匹配任何构造函数的嵌套对象,Jackson 可能找不到合适的构造函数并抛出错误。例如:

String json = "{\"street\": \蕋 Main St\", \"city\": \"Springfield\", \"extraField\": \"extraValue\"}";
Address address = mapper.readValue(json, Address.class);

在这种情况下,如果默认配置无法处理,extraField可能会导致错误。