讲师中心 微信公众号
AI工具推荐 视频效率加速

Jackson高级反序列化:处理冗余字段与非空值优先策略

风萱同学_7472

风萱同学_7472

发布时间:2025-12-05 18:08:41

|

450人浏览过

|

来源于php中文网

原创

Jackson高级反序列化:处理冗余字段与非空值优先策略

本文深入探讨了在使用jackson进行json反序列化时,如何有效处理包含多个冗余字段且需优先选择非空值的复杂场景。针对这一挑战,文章提供了两种核心解决方案:一是利用多个智能setter方法实现条件赋值,二是采用自定义converter进行解耦和灵活的数据转换。通过详细的代码示例和原理分析,旨在帮助开发者构建更健壮、可维护的jackson反序列化逻辑。

在现代应用开发中,与第三方系统集成是常态。然而,外部数据源的JSON结构有时会存在冗余,即多个字段可能承载相同的信息,并且这些字段的值有时会是null或空字符串。当我们需要将这些JSON数据映射到Java POJO时,Jackson的默认行为可能无法满足“优先选择第一个非空非空字符串值”的需求。例如,面对{"name": null, "full_name": "", "fullName": "name"}这样的JSON,我们希望最终的POJO字段能获取到"name"。本文将介绍两种实用的策略来解决这一问题。

1. 利用多重Setter方法实现智能赋值

Jackson允许我们为同一个POJO字段定义多个Setter方法,并通过@JsonSetter注解将它们分别映射到不同的JSON字段。结合自定义的逻辑,我们可以实现“智能”赋值,确保只有当当前字段值为空或null时,才接受新的非空值。

核心思想

为目标POJO字段定义一个主Setter方法,并在其中包含判断逻辑。然后,为每个冗余的JSON字段定义一个额外的Setter,这些Setter将调用主Setter进行赋值。

示例代码

首先,定义一个辅助的谓词(Predicate)来判断字符串是否为null或空。

import com.fasterxml.jackson.annotation.JsonSetter;
import java.util.function.Predicate;

public class MyPojo {

    // 可重用的谓词,用于检查字符串是否为null或空
    public static final Predicate<String> IS_NULL_OR_EMPTY =
        s -> s == null || s.isEmpty();

    private String name;

    // 主Setter方法,包含智能赋值逻辑
    @JsonSetter("name")
    public void setName(String name) {
        // 只有当当前name字段为null或空时,才更新为新值
        if (IS_NULL_OR_EMPTY.test(this.name)) {
            this.name = name;
        }
    }

    // 针对别名 "full_name" 的Setter
    @JsonSetter("full_name")
    public void setNameFromFullName(String name) {
        // 委托给主setName方法处理
        setName(name);
    }

    // 针对别名 "fullName" 的Setter
    @JsonSetter("fullName")
    public void setNameFromCamelCaseFullName(String name) {
        // 委托给主setName方法处理
        setName(name);
    }

    // Getter方法 (为简洁,此处省略其他boilerplate代码,如构造器、toString等)
    public String getName() {
        return name;
    }

    @Override
    public String toString() {
        return "MyPojo{name='" + name + "'}";
    }
}

使用示例

import com.fasterxml.jackson.databind.ObjectMapper;

public class JacksonSetterExample {
    public static void main(String[] args) throws Exception {
        String json1 = "{ \"name\" : null, \"full_name\" : \"\", \"fullName\" : \"nameValue\"}";
        String json2 = "{ \"name\" : \"primaryName\", \"full_name\" : \"\", \"fullName\" : \"anotherName\"}";
        String json3 = "{ \"full_name\" : \"onlyFullName\"}";

        ObjectMapper mapper = new ObjectMapper();

        MyPojo myPojo1 = mapper.readValue(json1, MyPojo.class);
        System.out.println("JSON 1 Output: " + myPojo1); // 预期: MyPojo{name='nameValue'}

        MyPojo myPojo2 = mapper.readValue(json2, MyPojo.class);
        System.out.println("JSON 2 Output: " + myPojo2); // 预期: MyPojo{name='primaryName'}

        MyPojo myPojo3 = mapper.readValue(json3, MyPojo.class);
        System.out.println("JSON 3 Output: " + myPojo3); // 预期: MyPojo{name='onlyFullName'}
    }
}

注意事项与局限性

  • 违反单一职责原则 (SRP):Setter方法通常只负责赋值,而这里它包含了额外的业务逻辑。这可能使代码难以理解和维护。
  • 污染领域模型:领域POJO中会包含额外的Setter方法,增加了类的复杂性,特别是在字段较多时。
  • 执行顺序:Jackson在反序列化时,如果JSON中存在多个映射到同一字段的别名,它会按照JSON中字段出现的顺序调用对应的Setter。因此,确保智能Setter的判断逻辑能够正确处理后续值的覆盖。

2. 通过自定义Converter实现解耦与灵活转换

当冗余字段较多,或者需要更复杂的转换逻辑时,自定义Jackson Converter是更优雅的选择。这种方法将转换逻辑从领域POJO中完全分离,保持了领域模型的纯净。

核心思想

Jackson的Converter用于将一种POJO类型(通常是辅助类型)转换为另一种POJO类型(目标领域类型)。我们可以定义一个辅助POJO,它直接映射JSON中的所有冗余字段,然后编写一个Converter,负责从这个辅助POJO中提取并组合出目标POJO。

概念辨析:Converter vs. Deserializer

  • Deserializer (JsonDeserializer):直接从JsonParser读取原始JSON流,并根据其内容构建目标POJO。它提供了对反序列化过程的底层控制。
  • Converter (StdConverter):在Jackson完成初步反序列化(将JSON映射到某个中间POJO)之后,将这个中间POJO转换为另一个目标POJO。它更侧重于对象间的类型转换,而不是底层的JSON解析。

示例代码

首先,定义一个辅助POJO来捕获所有可能的JSON字段。这里使用Java 16+的record类型简化定义。

jm-jsjkxyjs02-zlm-405
jm-jsjkxyjs02-zlm-405

使用约定式提交(Conventional Commits)生成标准化的 Git 提交信息。适用于以下场景: (1) 暂存更改时需要提交信息; (2) 审查 diff 时希望获得结构化摘要; (3) 编写 PR 描述或变更日志(changelog)条目; (4) 用户明确要求“为本次更改编写提交信息”。 该功能会分析已暂存/未暂存的 diff,并生成按类型(type)划分范围的提交信息。

下载
import com.fasterxml.jackson.annotation.JsonProperty;

// 辅助POJO,用于直接映射原始JSON中的所有冗余字段
public record AuxiliaryPojo(
    @JsonProperty("name") String name,
    @JsonProperty("full_name") String fullNameAlias,
    @JsonProperty("fullName") String camelCaseFullNameAlias
) {}

接下来,创建自定义Converter,负责将AuxiliaryPojo转换为MyPojo。

import com.fasterxml.jackson.databind.util.StdConverter;
import java.util.Arrays;
import java.util.function.Predicate;

// Converter:将AuxiliaryPojo转换为MyPojo
public class AuxiliaryPojoToMyPojoConverter extends StdConverter<AuxiliaryPojo, MyPojo> {

    // 谓词:判断字符串是否非null且非空
    public static final Predicate<String> IS_NOT_NULL_OR_EMPTY =
        s -> s != null && !s.isEmpty();

    @Override
    public MyPojo convert(AuxiliaryPojo source) {
        // 调用辅助方法,从多个字段中找到第一个非空非空字符串
        String resolvedName = findFirstNonNullOrEmpty(
            source.name(),
            source.fullNameAlias(),
            source.camelCaseFullNameAlias()
        );

        // 使用Builder模式构建目标MyPojo实例
        return MyPojo.builder()
            .name(resolvedName)
            .build();
    }

    // 辅助方法:从可变参数中找到第一个非null且非空字符串
    private String findFirstNonNullOrEmpty(String... args) {
        return Arrays.stream(args)
            .filter(IS_NOT_NULL_OR_EMPTY)
            .findFirst()
            .orElse(null); // 如果都没有找到,则返回null
    }
}

最后,在目标领域POJO上通过@JsonDeserialize注解指定Converter。为了演示简洁,我们使用Lombok的@Getter和@Builder注解。

import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import lombok.Builder;
import lombok.Getter;
import lombok.ToString;

// 目标领域POJO,保持简洁,不含冗余逻辑
@Getter
@Builder
@ToString
@JsonDeserialize(converter = AuxiliaryPojoToMyPojoConverter.class) // 指定Converter
public class MyPojo {
    private String name;
}

使用示例

import com.fasterxml.jackson.databind.ObjectMapper;

public class JacksonConverterExample {
    public static void main(String[] args) throws Exception {
        String json1 = "{ \"name\" : null, \"full_name\" : \"\", \"fullName\" : \"nameFromConverter\"}";
        String json2 = "{ \"full_name\" : \"onlyFullNameFromConverter\"}";

        ObjectMapper mapper = new ObjectMapper();

        // 直接反序列化MyPojo,Jackson会自动调用Converter
        MyPojo myPojo1 = mapper.readValue(json1, MyPojo.class);
        System.out.println("JSON 1 Output: " + myPojo1); // 预期: MyPojo{name='nameFromConverter'}

        MyPojo myPojo2 = mapper.readValue(json2, MyPojo.class);
        System.out.println("JSON 2 Output: " + myPojo2); // 预期: MyPojo{name='onlyFullNameFromConverter'}
    }
}

注意事项与优势

  • 领域模型纯净:MyPojo不再包含任何与反序列化逻辑相关的代码,保持了其作为领域对象的简洁性。
  • 职责分离:AuxiliaryPojo负责捕获原始JSON结构,Converter负责转换逻辑,职责明确。
  • 可扩展性:当需要处理更多冗余字段或更复杂的转换规则时,只需修改AuxiliaryPojo和Converter,而无需触及领域POJO。
  • 性能考量:相比于直接的JsonDeserializer,Converter会先将JSON反序列化为中间类型,再进行转换,这可能带来轻微的性能开销。但对于大多数应用场景,这种开销通常可以忽略不计,其带来的代码清晰度收益更高。

总结与选择

面对Jackson反序列化中冗余字段和非空值优先选择的挑战,两种方案各有优劣:

  1. 多重Setter方法:

    • 优点:实现简单直接,适用于字段数量不多、逻辑不复杂的场景。
    • 缺点:污染领域模型,可能违反单一职责原则,不易维护和扩展。
  2. 自定义Converter:

    • 优点:彻底解耦反序列化逻辑与领域模型,代码结构清晰,易于维护和扩展,符合面向对象设计原则。
    • 缺点:需要额外定义辅助POJO和Converter类,增加了少量代码量。

在实际项目中,如果反序列化逻辑较为复杂,或者需要处理大量冗余字段,强烈推荐使用自定义Converter。它能让你的领域模型保持纯净,并提供更好的可维护性和可扩展性。对于非常简单的、少量字段的场景,多重Setter方法或许可以作为快速解决方案,但仍需权衡其对代码质量的影响。通过合理选择和应用这些策略,开发者可以更有效地利用Jackson处理复杂的JSON反序列化需求。

热门AI工具

更多
Loomy
Loomy Hot

一款AI工具,主要用于科大讯飞发布的桌面级 AI 助理,比 OpenClaw 更易用、更安全!,适合需要提升相关任务效率的用户。

SkildArt
SkildArt Hot

SkildArt是一款AI文本写作工具,一站式 AI 视觉创作平台。

豆包大模型

豆包大模型是一款由字节跳动推出的企业级大语言模型服务平台。

音述AI
音述AI Hot

一款AI音频处理工具,主要用于音述AI是一个以“用声音述说故事”为核心的 AI 音乐创作与声音分享社区,适合需要提升相关任务效率的用户。

DeepSeek

DeepSeek是一款面向对话、写作、编程和推理场景的AI大模型工具。

Lovart
Lovart Hot

一款面向视觉设计创作的AI设计平台,可通过智能体和画布工作流辅助制作海报、Logo、网页、PPT及其他视觉内容。

码上飞
码上飞 Hot

一款基于自然语言的AI应用开发平台,可根据用户描述自动生成应用程序,帮助个人和团队快速完成应用原型与开发。

WorkBuddy

一款AI办公效率工具,主要用于腾讯云推出的AI原生桌面智能体工作台,适合需要提升相关任务效率的用户。

Laper
Laper Hot

Laper是专为编剧、导演和制片人推出的 AI 原生剧本创作工具。

相关专题

更多
json数据格式
json数据格式

JSON是一种轻量级的数据交换格式。本专题为大家带来json数据格式相关文章,帮助大家解决问题。

2275

2023.08.07

json是什么
json是什么

JSON是一种轻量级的数据交换格式,具有简洁、易读、跨平台和语言的特点,JSON数据是通过键值对的方式进行组织,其中键是字符串,值可以是字符串、数值、布尔值、数组、对象或者null,在Web开发、数据交换和配置文件等方面得到广泛应用。本专题为大家提供json相关的文章、下载、课程内容,供大家免费下载体验。

3702

2023.08.23

jquery怎么操作json
jquery怎么操作json

操作的方法有:1、“$.parseJSON(jsonString)”2、“$.getJSON(url, data, success)”;3、“$.each(obj, callback)”;4、“$.ajax()”。更多jquery怎么操作json的详细内容,可以访问本专题下面的文章。

1236

2023.10.13

go语言处理json数据方法
go语言处理json数据方法

本专题整合了go语言中处理json数据方法,阅读专题下面的文章了解更多详细内容。

4259

2025.09.10

c语言中null和NULL的区别
c语言中null和NULL的区别

c语言中null和NULL的区别是:null是C语言中的一个宏定义,通常用来表示一个空指针,可以用于初始化指针变量,或者在条件语句中判断指针是否为空;NULL是C语言中的一个预定义常量,通常用来表示一个空值,用于表示一个空的指针、空的指针数组或者空的结构体指针。

549

2023.09.22

java中null的用法
java中null的用法

在Java中,null表示一个引用类型的变量不指向任何对象。可以将null赋值给任何引用类型的变量,包括类、接口、数组、字符串等。想了解更多null的相关内容,可以阅读本专题下面的文章。

1698

2024.03.01

go语言 面向对象
go语言 面向对象

本专题整合了go语言面向对象相关内容,阅读专题下面的文章了解更多详细内容。

7435

2025.09.05

java面向对象
java面向对象

本专题整合了java面向对象相关内容,阅读专题下面的文章了解更多详细内容。

362

2025.11.27

Postman身份认证怎么配置
Postman身份认证怎么配置

本专题从实际API接口调试场景出发,讲解Postman身份认证的完整配置流程,包括常见认证协议介绍、授权参数设置、访问令牌填写、OAuth 2.0授权及认证失败排查,帮助用户正确完成接口授权与访问验证。

0

2026.11.06

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
dev.java 官方:Learn Java
dev.java 官方:Learn Java

共0课时 | 0人学习

Java JDBC数据库连接官方教程
Java JDBC数据库连接官方教程

共0课时 | 0人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn