Jackson中的ConstructorDetector指南

banq發表於2024-06-13

使用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 = <font>"{\&#34firstName\&#34: \&#34John\&#34, \&#34lastName\&#34: \&#34Doe\&#34, \&#34age\&#34: 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(<font>"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 = <font>"\&#34Hello, world!\&#34";
    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 = <font>"{\&#34value\&#34: \&#34Hello, world!\&#34, \&#34extraField\&#34: \&#34extraValue\&#34}";
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(<font>"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 = <font>"{\&#34value\&#34: \&#34Laptop\&#34, \&#34price\&#34: 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 = <font>"{\&#34value\&#34: \&#34Laptop\&#34}";
Product product = mapper.readValue(json, Product.class);

由於缺少價格欄位,這將導致錯誤。

String json = <font>"{\&#34value\&#34: \&#34Laptop\&#34, \&#34price\&#34: 999.99, \&#34extraField\&#34: \&#34extraValue\&#34}";
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 = <font>"{\&#34street\&#34: \&#34123 Main St\&#34, \&#34city\&#34: \&#34Springfield\&#34}";
    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 = <font>"{\&#34street\&#34: \&#34123 Main St\&#34, \&#34city\&#34: \&#34Springfield\&#34, \&#34extraField\&#34: \&#34extraValue\&#34}";
Address address = mapper.readValue(json, Address.class);

在這種情況下,如果預設配置無法處理,extraField可能會導致錯誤。

相關文章