1. Overview
1.概述
JSON is a de-facto standard for RESTful applications. Spring uses the Jackson library to convert objects into and from JSON seamlessly. However, sometimes, we want to customize the conversion and provide specific rules.
JSON 是 RESTful 应用程序的事实标准。Spring 使用 Jackson 库将对象无缝转换为 JSON 或从 JSON 转换而来。但是,有时我们希望自定义转换并提供特定规则。
One such thing is to ignore empty or null values from responses or requests. This might provide performance benefits as we don’t need to send empty values back and forth. Also, this can make our APIs more straightforward.
其中之一就是忽略响应或请求中的空值或 null 值。这可能会带来性能上的优势,因为我们不需要来回发送空值。
In this tutorial, we’ll learn how to leverage Jackson mapping to simplify our REST interactions.
在本教程中,我们将学习如何利用 Jackson 映射来简化 REST 交互。
2. Null Values
2.空值
While sending or receiving requests, we often can see the values set to nulls. However, usually, it doesn’t provide us with any useful information as, in most cases, this is a default value for non-defined variables or fields.
在发送或接收请求时,我们经常可以看到值被设置为 空。但是,这通常不会为我们提供任何有用的信息,因为在大多数情况下,这是非定义变量或字段的默认值。
Also, the fact that we allow null values passed in JSON complicates the validation process. We can skip the validation and set it to default if the value isn’t present. However, if the value is present, we need to do additional checks to identify if it’s null and if it’s possible to convert it to some reasonable representation.
此外,我们允许在 JSON 中传递 null 值,这也使验证过程变得复杂。如果值不存在,我们可以跳过验证并将其设置为默认值。但是,如果值存在,我们就需要进行额外的检查,以确定它是否为 null 以及是否有可能将其转换为某种合理的表示形式。
Jackson provides a convenient way to configure it directly in our classes. We’ll use Include.NON_NULL. It can be used on the class level if the rule applies to all the fields, or we can use it more granularly on the fields, getters, and setters. Let’s consider the following Employee class:
Jackson 提供了一种方便的方法,可以直接在我们的类中进行配置。我们将使用 Include.NON_NULL 。如果该规则适用于所有字段,则可在类级别使用该规则,我们也可以在字段、getters 和 setters 中更细粒度地使用该规则。让我们来看看下面的 Employee 类:
@JsonInclude(Include.NON_NULL)
public class Employee {
private String lastName;
private String firstName;
private long id;
// constructors, getters and setters
}
If any of the fields is null, and we’re talking only about reference fields, they won’t be included in the generated JSON:
如果任何字段为空,而且我们说的只是引用字段,那么生成的 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);
}
Sometimes, we want to replicate a similar behavior for null-like fields, and Jackson also provides a way to handle them.
有时,我们希望为类似 null 的字段复制类似的行为,Jackson 也提供了处理它们的方法。
3. Absent Values
3.缺席值
Empty Optional is, technically, a non-null value. However, passing a wrapper for non-existent values in requests or responses makes little sense. The previous annotation won’t handle this case and will try to add some information about the wrapper itself:
从技术上讲,空 Optional 是一个 non-null 值。但是,在请求或响应中为不存在的值传递一个包装器意义不大。前面的注解不会处理这种情况,而是会尝试添加一些有关包装器本身的信息:
{
"lastName": "John",
"firstName": "Doe",
"id": 1,
"salary": {
"empty": true,
"present": false
}
}
Let’s imagine that every employee in our company can expose their salary if they want to do so:
让我们设想一下,如果我们公司的每一位员工愿意,他们都可以公开自己的工资:
@JsonInclude(Include.NON_ABSENT)
public class Employee {
private String lastName;
private String firstName;
private long id;
private Optional<Salary> salary;
// constructors, getters and setters
}
We can handle it with custom getters and setters that return null values. However, it would complicate the API and disregard the idea behind using Optionals in the first place. To ignore empty Optionals, we can use Include.NON_ABSENT:
我们可以使用返回 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 handles empty Optional values and nulls so that we can use it for both scenarios.
Include.NON_ABSENT可处理空选项值和空值,因此我们可以在两种情况下使用它。
4. Empty Values
4.空值
Should we include empty strings or empty collections in the generated JSON? In most cases, it doesn’t make sense. Setting them to nulls or wrapping them with Optionals might not be a good idea and can complicate the interactions with the objects.
我们是否应该在生成的 JSON 中包含空字符串或空集合?在大多数情况下,这并不合理。将它们设置为 nulls 或使用 Optionals 对它们进行封装可能不是一个好主意,而且会使与对象的交互变得复杂。
Let’s consider some additional information about our employees. As we’re working in an international organization, it would be reasonable to assume that an employee might want to add a phonetic version of their name. Also, they might provide a phone number or numbers to allow others to get in touch with them:
让我们考虑一下员工的其他信息。由于我们是在一个国际组织中工作,我们有理由认为,员工可能希望添加自己姓名的拼音版本。此外,他们还可能提供一个或多个电话号码,以便他人与他们取得联系:
@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
}
We can use Include.NON_EMPTY to exclude the values if they’re empty. This configuration ignores null and absent values as well:
如果值为空,我们可以使用 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);
}
As was mentioned previously, all these annotations can be used more granularly, and we can even apply different strategies to different fields. Additionally, we can configure our mapper globally to apply this rule to any conversion.
如前所述,所有这些注释都可以更精细地使用,我们甚至可以对不同的字段应用不同的策略。此外,我们还可以配置映射器globally,将此规则应用于任何转换。
5. Custom Mappers
5.自定义绘图仪
If the above strategies aren’t flexible enough for our needs or need to support specific conventions, we should use Include.CUSTOM or implement a custom serializer:
如果上述策略不够灵活,无法满足我们的需求,或者需要支持特定的约定,我们应该使用 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();
}
}
6. Conclusion
6.结论
Jackson and Spring can help us develop RESTul applications with minimal configuration from our side. Inclusion strategies can simplify our APIs and reduce the amount of boilerplate code. At the same time, if the default solutions are too restrictive or inflexible, we can extend using custom mappers or filters.
Jackson 和 Spring 可以帮助我们开发 RESTul 应用程序,只需从我们这边进行最少的配置。包含策略可以简化我们的 API 并减少模板代码的数量。同时,如果默认解决方案限制性过强或不够灵活,我们可以使用自定义映射器或过滤器进行扩展。
As usual, all the code from this tutorial is available over on GitHub.
与往常一样,本教程中的所有代码都可以在 GitHub 上获取。