使用 Spring 和 Jackson 時刪除 JSON 回應中的空對象
1. 概述
JSON 是 RESTful 應用程式事實上的標準。 Spring 使用 Jackson 函式庫將物件與 JSON 無縫轉換。然而,有時,我們想要自訂轉換並提供特定的規則。
其中之一就是忽略回應或請求中的空值或空值。這可能會帶來效能優勢,因為我們不需要來回發送空值。此外,這可以使我們的 API 更加簡單。
在本教程中,我們將學習如何利用 Jackson 映射來簡化 REST 互動。
2. 空值
在發送或接收請求時,我們經常會看到值被設定為nulls.
但是,通常它不會為我們提供任何有用的信息,因為在大多數情況下,這是未定義變數或欄位的預設值。
此外,我們允許在 JSON 中傳遞null
值這一事實使驗證過程變得複雜。如果該值不存在,我們可以跳過驗證並將其設為預設值。但是,如果該值存在,我們需要進行額外的檢查來確定它是否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,
並且我們僅討論引用字段,則它們不會包含在生成的 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. 缺失值
從技術上講,空Optional
是一個non-null
值。但是,為請求或回應中不存在的值傳遞包裝器沒有什麼意義。前面的註釋不會處理這種情況,並將嘗試添加一些有關包裝器本身的資訊:
{
"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
}
我們可以使用傳回null
值的自訂 getter 和 setter 來處理它。然而,這會使 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. 自訂映射器
如果上述策略不足以滿足我們的需求或需要支援特定約定,我們應該使用Include.CUSTOM
或實作自訂序列化器:
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();
}
}
六,結論
Jackson 和 Spring 可以幫助我們以最少的配置開發 RESTul 應用程式。包含策略可以簡化我們的 API 並減少樣板程式碼的數量。同時,如果預設解決方案限制太多或不靈活,我們可以使用自訂映射器或過濾器進行擴充。
與往常一樣,本教程中的所有程式碼都可以在 GitHub 上取得。