
本文旨在解决使用 jackson 反序列化 json 数据时,当多个字段可能表示同一信息,且其中部分字段可能为 `null` 或空字符串时,如何优先选择非空值的挑战。我们将探讨两种核心策略:通过定义多个智能 `setter` 方法并结合 `@jsonsetter` 注解,以及利用自定义 `converter` 结合辅助 pojo 和 `@jsondeserialize` 注解,实现灵活且健壮的数据映射,确保数据完整性。
在 Spring Boot 应用中,使用 Jackson 处理来自第三方服务或遗留系统的 JSON 数据时,我们经常会遇到 JSON 结构冗余的问题。例如,一个逻辑上的“名称”信息可能在 JSON 中以 name、full_name 或 fullName 等多个字段表示。更复杂的是,这些字段可能并非总是同时存在或非空,有时只有一个字段包含有效数据,而其他字段为 null 或空字符串。Jackson 的 @JsonAlias 注解虽然可以处理多个别名,但它通常不会智能地忽略 null 或空值,而是按照其内部优先级选择一个值,这可能导致最终 POJO 字段被 null 或空值覆盖。
本教程将详细介绍两种有效策略,以确保 Jackson 在这种场景下能够优先选择非 null 或非空字符串的字段值。
第一种方法是为每个可能包含目标信息的 JSON 字段定义一个独立的 setter 方法,并使用 @JsonSetter 注解将其映射到对应的 JSON 属性名。核心思想是在这些 setter 方法中引入逻辑,仅当目标 POJO 字段当前为 null 或空时才更新其值。
import com.fasterxml.jackson.annotation.JsonSetter;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.function.Predicate;
public class MyPojo {
// 定义一个谓词,用于检查字符串是否为null或空
public static final Predicate<String> NULL_OR_EMPTY =
s -> s == null || s.isEmpty();
private String name;
// 主setter方法,包含更新逻辑
@JsonSetter("name")
public void setName(String name) {
// 只有当当前name字段为null或空时才更新
if (NULL_OR_EMPTY.test(this.name)) {
this.name = name;
}
}
// 辅助setter方法,映射到"full_name",并委托给主setName方法
@JsonSetter("full_name")
public void setFullNameAlias(String name) {
setName(name);
}
// 辅助setter方法,映射到"fullName",并委托给主setName方法
@JsonSetter("fullName")
public void setCamelCaseFullNameAlias(String name) {
setName(name);
}
// Getter和toString方法,便于测试
public String getName() {
return name;
}
@Override
public String toString() {
return "MyPojo{name='" + name + "'}";
}
// 示例用法
public static void main(String[] args) throws Exception {
String json1 = "{ \"name\" : null, \"full_name\" : \"\", \"fullName\" : \"John Doe\"}";
String json2 = "{ \"name\" : \"Jane Doe\", \"full_name\" : \"\", \"fullName\" : null}";
String json3 = "{ \"name\" : null, \"full_name\" : \"Another Name\", \"fullName\" : null}";
ObjectMapper mapper = new ObjectMapper();
MyPojo myPojo1 = mapper.readValue(json1, MyPojo.class);
System.out.println("JSON 1 Output: " + myPojo1); // Expected: MyPojo{name='John Doe'}
MyPojo myPojo2 = mapper.readValue(json2, MyPojo.class);
System.out.println("JSON 2 Output: " + myPojo2); // Expected: MyPojo{name='Jane Doe'}
MyPojo myPojo3 = mapper.readValue(json3, MyPojo.class);
System.out.println("JSON 3 Output: " + myPojo3); // Expected: MyPojo{name='Another Name'}
}
}第二种更优雅的解决方案是利用 Jackson 的 Converter 机制。Converter 允许我们在反序列化过程中将一种 POJO 类型转换为另一种 POJO 类型。这种方法将复杂的选择逻辑从领域模型中分离出来,提高了代码的清晰度和可维护性。
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.util.StdConverter;
import lombok.Builder;
import lombok.Getter;
import java.util.Arrays;
import java.util.function.Predicate;
// 1. 定义辅助POJO,用于捕获所有可能的JSON字段
// 使用record简化数据类定义,需要Java 16+
record AuxiliaryPojo(
@JsonProperty("name") String name,
@JsonProperty("full_name") String name1,
@JsonProperty("fullName") String name2
) {}
// 2. 创建自定义Converter
public class AuxiliaryPojoToMyPojoConverter extends StdConverter<AuxiliaryPojo, MyPojo> {
public static final Predicate<String> NOT_NULL_OR_EMPTY =
s -> s != null && !s.isEmpty();
@Override
public MyPojo convert(AuxiliaryPojo v) {
// 在转换逻辑中,查找第一个非null且非空的字符串
String finalName = findMatching(v.name(), v.name1(), v.name2());
// 使用Builder模式创建MyPojo实例
return MyPojo.builder().name(finalName).build();
}
// 辅助方法,用于从多个字符串中查找第一个非null非空值
private String findMatching(String... args) {
return Arrays.stream(args)
.filter(NOT_NULL_OR_EMPTY)
.findFirst()
.orElse(null); // 如果所有都为null或空,则返回null
}
}
// 3. 目标POJO,应用Converter
@Getter
@Builder
@JsonDeserialize(converter = AuxiliaryPojoToMyPojoConverter.class)
public static class MyPojo {
private String name;
@Override
public String toString() {
return "MyPojo{name='" + name + "'}";
}
// 示例用法
public static void main(String[] args) throws Exception {
String json1 = "{ \"name\" : null, \"full_name\" : \"\", \"fullName\" : \"John Doe\"}";
String json2 = "{ \"name\" : \"Jane Doe\", \"full_name\" : \"\", \"fullName\" : null}";
String json3 = "{ \"name\" : null, \"full_name\" : \"Another Name\", \"fullName\" : null}";
ObjectMapper mapper = new ObjectMapper();
MyPojo myPojo1 = mapper.readValue(json1, MyPojo.class);
System.out.println("JSON 1 Output: " + myPojo1); // Expected: MyPojo{name='John Doe'}
MyPojo myPojo2 = mapper.readValue(json2, MyPojo.class);
System.out.println("JSON 2 Output: " + myPojo2); // Expected: MyPojo{name='Jane Doe'}
MyPojo myPojo3 = mapper.readValue(json3, MyPojo.class);
System.out.println("JSON 3 Output: " + myPojo3); // Expected: MyPojo{name='Another Name'}
}
}当 Jackson 反序列化遇到多别名 JSON 字段且需要优先选择非 null 或非空值时,我们有两种主要策略:
通常情况下,推荐使用自定义 Converter 的方法,因为它提供了更强大的灵活性和更好的代码组织结构,尤其是在处理复杂的反序列化逻辑时。根据项目的具体需求和复杂性,选择最适合的策略。
以上就是Jackson 处理多别名 JSON 字段:优先选择非空值的策略的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号