1. 概述
JSON 是一種事實上的標準,用於 RESTful 應用程序。Spring 使用 Jackson 庫無縫地將對象轉換為 JSON 格式,反之亦然。然而,有時我們希望自定義轉換並提供特定的規則。
例如,忽略響應或請求中的空值或空值。 這可能會帶來性能優勢,因為我們不需要發送空值來回。 此外,這也有助於使我們的 API 更簡潔。
在本教程中,我們將學習如何利用 Jackson 映射來簡化我們的 REST 交互。
2. 空值
在發送或接收請求時,我們經常會看到值被設置為 nulls. 然而,通常情況下,這並不能為我們提供任何有用的信息,因為在大多數情況下,這只是未定義變量或字段的默認值。
此外,我們允許將 null 值通過 JSON 傳遞,這會使驗證過程變得複雜。我們可以跳過驗證並將其設置為默認值,如果值不存在。但是,如果值存在,則需要進行額外的檢查以確定它是否為 null 並且是否可以將其轉換為一些合理的表示形式。
Jackson 提供了在我們的類中直接配置它的便捷方式。 我們將使用 Include.NON_NULL. 它可以用於類級別,如果規則適用於所有字段,或者我們可以更精細地在字段、getter 和 setter 上使用它。 讓我們考慮以下 Employee 類:
@JsonInclude(Include.NON_NULL)
public class Employee {
private String lastName;
private String firstName;
private long id;
// constructors, getters and setters
}如果任何字段為 null</em/>,且僅討論引用字段,則不會包含在生成的JSON中:
@ParameterizedTest
@MethodSource
void giveEndpointWhenSendEmployeeThanReceiveThatUserBackIgnoringNullValues(Employee expected) throws Exception {
MvcResult result = sendRequestAndGetResult(expected, USERS);
String response = result.getResponse().getContentAsString();
validateJsonFields(expected, response);
}
private void validateJsonFields(Employee expected, String response) throws JsonProcessingException {
JsonNode jsonNode = mapper.readTree(response);
Predicate<Field> nullField = s -> isFieldNull(expected, s);
List<String> nullFields = filterFieldsAndGetNames(expected, nullField);
List<String> nonNullFields = filterFieldsAndGetNames(expected, nullField.negate());
nullFieldsShouldBeMissing(nullFields, jsonNode);
nonNullFieldsShouldNonBeMissing(nonNullFields, jsonNode);
}有時,我們希望複製對 null 類似字段的行為,Jackson 也提供了處理這些字段的方法。
3. 缺失值
空值(Empty)實際上是一個可選對象,從技術上講,它是一個非空值。但是,在請求或響應中傳遞表示不存在值的包裝器,在實踐中沒有意義。之前的註解無法處理這種情況,並且會嘗試添加關於包裝器本身的某些信息:
{
"lastName": "John",
"firstName": "Doe",
"id": 1,
"salary": {
"empty": true,
"present": false
}
}讓我們設想一下,如果我們的公司中的每位員工都願意公開他們的薪資,那麼會怎樣:
@JsonInclude(Include.NON_ABSENT)
public class Employee {
private String lastName;
private String firstName;
private long id;
private Optional<Salary> salary;
// constructors, getters and setters
}我們可以使用自定義的 Getter 和 Setter 方法來返回 null 值。 然而,這會使 API 複雜化,並且會忽略使用 Optionals 的初衷。 為了忽略空 Optionals,我們可以使用 Include.NON_ABSENT:
private void validateJsonFields(Employee expected, String response) throws JsonProcessingException {
JsonNode jsonNode = mapper.readTree(response);
Predicate<Field> nullField = s -> isFieldNull(expected, s);
Predicate<Field> absentField = s -> isFieldAbsent(expected, s);
List<String> nullOrAbsentFields = filterFieldsAndGetNames(expected, nullField.or(absentField));
List<String> nonNullAndNonAbsentFields = filterFieldsAndGetNames(expected, nullField.negate().and(absentField.negate()));
nullFieldsShouldBeMissing(nullOrAbsentFields, jsonNode);
nonNullFieldsShouldNonBeMissing(nonNullAndNonAbsentFields, jsonNode);
}Include.NON_ABSENT 處理空 Optional 值和 nulls,以便我們能夠在兩種情況下使用它。
4. 空值
我們是否應該在生成的 JSON 中包含空字符串或空集合? 在大多數情況下,這沒有意義。 將它們設置為 nulls 或用 Optionals 包裹也可能不是一個好主意,並且可能會使與對象之間的交互變得複雜。
讓我們考慮一些關於我們員工的額外信息。 鑑於我們公司在國際化環境中運作,員工可能希望添加他們名字的音譯版本,並且他們可能提供電話號碼或電話號碼以便他人聯繫他們。
@JsonInclude(Include.NON_EMPTY)
public class Employee {
private String lastName;
private String firstName;
private long id;
private Optional<Salary> salary;
private String phoneticName = "";
private List<PhoneNumber> phoneNumbers = new ArrayList<>();
// constructors, getters and setters
}我們可以使用 Include.NON_EMPTY 來排除空值。 此配置還忽略了 null 和缺失值:
private void validateJsonFields(Employee expected, String response) throws JsonProcessingException {
JsonNode jsonNode = mapper.readTree(response);
Predicate<Field> nullField = s -> isFieldNull(expected, s);
Predicate<Field> absentField = s -> isFieldAbsent(expected, s);
Predicate<Field> emptyField = s -> isFieldEmpty(expected, s);
List<String> nullOrAbsentOrEmptyFields = filterFieldsAndGetNames(expected, nullField.or(absentField).or(emptyField));
List<String> nonNullAndNonAbsentAndNonEmptyFields = filterFieldsAndGetNames(expected,
nullField.negate().and(absentField.negate().and(emptyField.negate())));
nullFieldsShouldBeMissing(nullOrAbsentOrEmptyFields, jsonNode);
nonNullFieldsShouldNonBeMissing(nonNullAndNonAbsentAndNonEmptyFields, jsonNode);
}
正如之前所述,這些註釋可以更精細地使用,並且我們甚至可以針對不同的字段應用不同的策略。此外,我們還可以全局配置映射器,以將該規則應用於任何轉換。
5. 自定義映射器
如果上述策略無法滿足我們的需求或需要支持特定的約定,我們應該使用 <em Include.CUSTOM</em> 或實現自定義序列化器:
public class CustomEmployeeSerializer extends StdSerializer<Employee> {
@Override
public void serialize(Employee employee, JsonGenerator gen, SerializerProvider provider)
throws IOException {
gen.writeStartObject();
// Custom logic to serialize other fields
gen.writeEndObject();
}
}6. 結論
Jackson 和 Spring 可以幫助我們開發具有最小配置需求的 RESTful 應用。 採用合適的集成策略可以簡化我們的 API 並減少冗餘代碼量。 同時,如果默認解決方案過於嚴格或不靈活,我們可以通過自定義映射器或過濾器進行擴展。