1. 簡介
與 Jackson 協作的關鍵之一是理解如何將 JSON 數據映射到 Java 對象,這通常涉及使用構造函數。此外,ConstructorDetector 是 Jackson 中的關鍵組件,它會影響在反序列化過程中構造函數的選擇。
在本教程中,我們將詳細探討 ConstructorDetector,解釋其目的、配置和用法。
2. 構造檢測器:概述
構造檢測器 是 Jackson 的數據綁定模塊中的一項功能,它有助於確定在反序列化過程中,對類構造函數進行哪些考慮。Jackson 使用構造函數來實例化對象並使用 JSON 數據填充其字段。
構造檢測器 允許我們自定義並控制 Jackson 應使用哪些構造函數,從而在反序列化過程中提供更大的靈活性。
3. Configuring the ConstructorDetector
Jackson provides several predefined ConstructorDetector configurations, including USE_PROPERTIES_BASED, USE_DELEGATING, EXPLICIT_ONLY, and DEFAULT.
3.1. USE_PROPERTIES_BASED
This configuration is useful when our class has a constructor that matches the JSON properties. Let’s take a simple practical example:
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;
}
}
In this scenario, the class User has the properties firstName, lastName, and age. Jackson will look for a constructor in User with parameters that match these properties, which it finds in the provided User(String firstName, String lastName, int age) constructor.
Now, when deserializing JSON to a Java object using Jackson with ConstructorDetector.USE_PROPERTIES_BASED, Jackson will utilize the constructor that best matches the properties in the JSON object:
@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());
}
Here, the string named json represents a JSON object with properties firstName, lastName, and age, which correspond to the constructor parameters of the UserWhen deserializing the JSON using the mapper.Jackson will use the readValue(json, User.class) method to utilize the constructor with parameters matching the JSON properties.
If the JSON contains additional fields that aren’t present in the class, Jackson will ignore those fields without throwing an error. For example:
String json = "{\"firstName\": \"John\", \"lastName\": \"Doe\", \"age\": 25, \"extraField\": \"extraValue\"}";
User user = mapper.readValue(json, User.class);
In this case, the extraField is ignored. However, if the constructor parameters don’t match exactly with the JSON properties, Jackson may fail to find a suitable constructor and throw an error.
3.2. USE_DELEGATING
The USE_DELEGATING configuration allows Jackson to delegate object creation to a single-argument constructor. This can be beneficial when the JSON data structure aligns with the structure of a single parameter, enabling concise object creation.
Consider a use case where we have a class StringWrapper that wraps a single string value:
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;
}
}
The StringWrapper class has a single-parameter constructor annotated with @JsonCreator and @JsonProperty, indicating that Jackson should use delegation for object creation.
Let’s deserialize JSON to a Java object using Jackson with ConstructorDetector.USE_DELEGATING:
@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());
}
Here, we deserialize a JSON string value “Hello, world!” to a StringWrapper object using Jackson with ConstructorDetector.USE_DELEGATING. Jackson utilizes the single-parameter constructor of StringWrapper, correctly mapping the JSON string value.
Jackson will throw an error if the JSON structure doesn’t align with the single-parameter constructor. For example:
String json = "{\"value\": \"Hello, world!\", \"extraField\": \"extraValue\"}";
StringWrapper wrapper = mapper.readValue(json, StringWrapper.class);
In this case, the extraField could cause an error if the default configuration can’t handle it.
3.3. EXPLICIT_ONLY
This configuration ensures only explicitly annotated constructors are used. Furthermore, it provides strict control over constructor selection, allowing developers to specify which constructors Jackson should consider during deserialization.
Consider a scenario where the class Product represents a product with a name and price:
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;
}
}
This class has a constructor annotated with @JsonCreator, indicating that Jackson should use explicit constructor-based instantiation during deserialization.
Let’s see the deserialization process:
@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);
}
In this test method, we utilize the ConstructorDetector.EXPLICIT_ONLY configuration to deserialize a JSON object representing a product to a Product object. Only the annotated constructor of the Product class will be considered.
Jackson will throw an error if the JSON object has additional fields not present in the constructor or is missing required fields. For example:
String json = "{\"value\": \"Laptop\"}";
Product product = mapper.readValue(json, Product.class);
This will result in an error because the price field is missing.
String json = "{\"value\": \"Laptop\", \"price\": 999.99, \"extraField\": \"extraValue\"}";
Product product = mapper.readValue(json, Product.class);
This will also result in an error due to an unexpected field extraField.
3.4. DEFAULT
The DEFAULT configuration provides a balanced approach by considering various strategies for constructor selection. This type of configuration aims to select constructors that align with the JSON structure while considering custom annotations and other configuration options.
Consider a scenario where a class Address represents a postal 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;
}
}
The Address class has a constructor with parameters matching the JSON properties street and city.
Let’s deserialize JSON to a Java object using Jackson with ConstructorDetector.DEFAULT:
@Test
public void givenAddressJson_whenUsingDefault_thenCorrect() throws Exception {
String json = "{\"street\": \"123 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 employs its default heuristic to select the constructor that matches the JSON structure, ensuring accurate object instantiation from JSON data.
If the JSON structure is more complex or includes nested objects that don’t directly match any constructor, Jackson may not find a suitable constructor and throw an error. For example:
String json = "{\"street\": \"123 Main St\", \"city\": \"Springfield\", \"extraField\": \"extraValue\"}";
Address address = mapper.readValue(json, Address.class);
In this case, the extraField could cause an error if the default configuration can’t handle it.
4. 結論
綜上所述,理解 ConstructorDetector 在 Jackson 中的目的和配置對於在反序列化過程中將 JSON 數據映射到 Java 對象至關重要。