Java 中 null 值在 JSON 输出时丢失的坑:一次 Object 参数 + Fastjson 多态的血泪教训

大家好,我是G探险者!

在实际开发中,我们经常会遇到 Java 对象需要序列化成 JSON 字符串的场景。然而,如果使用不当,很可能会出现 null 字段"神秘消失" 的情况。本文将通过一个真实案例,完整分析问题成因及解决方案。

一、问题背景

在项目中,我们有一个统一响应类 InvokeResult,构造方法如下:

java 复制代码
public InvokeResult(StatusCode statusCode, Object result) {
    this.code = statusCode.getCode();
    this.message = statusCode.getReasonPhrase();
    this.result = result;
}

按理说,result 是个 Object,谁传什么就按原样存。 但在一次调试中,我发现 result 内部很多字段值为 null,Debug 里都能看到这些字段,可一旦调用:

java 复制代码
result.getResult().toString();

这些 null 字段就凭空消失了。


二、现象描述

Debug 截图(简化版):

json 复制代码
{
    "monitor_level": "01",
    "input_user_id": null,
    "check_user_id": null,
    "mod_datetime": null,
    "post_code": null
}
  • 在调试器中 :这些 null 字段清清楚楚地存在。
  • 在 toString() 输出后:只剩下非 null 的字段,所有 null 都消失。

三、定位的"卡点"

这个问题根因并不复杂,但我定位过程却耗费了不少时间,原因在于:

  • InvokeResult 的构造方法参数类型是 Object,所以第一反应不会想到它是个 JSON 对象。
  • 实际调用过程中,result 传入的是 com.alibaba.fastjson.JSONObject
  • 多态的"隐身攻击"Object.toString() 在运行时,其实会执行 JSONObject 重写的 toString() 方法,而不是 java.lang.Object 原始实现。

这就导致了我一开始一直以为只是简单的字符串化操作,没想到背后走的是 Fastjson 的序列化逻辑。


四、真相揭秘

  1. Fastjson 的 JSONObject 重写了 toString()

    java 复制代码
    @Override
    public String toString() {
        return JSON.toJSONString(this);
    }
  2. Fastjson 默认过滤 null

    java 复制代码
    JSON.toJSONString(obj); // 默认不输出 null
  3. 所以当你调用:

    java 复制代码
    result.getResult().toString();

    实际等价于:

    java 复制代码
    JSON.toJSONString(result.getResult());

    而不是普通的 Object.toString(),null 自然就被"优化"掉了。


五、解决方案

方案 1:显式保留 null

java 复制代码
return JSON.toJSONString(result.getResult(), SerializerFeature.WriteMapNullValue);

方案 2:避免直接用 JSONObject 做业务数据

java 复制代码
Map<String, Object> map = new HashMap<>();
map.put("input_user_id", null);
...
new InvokeResult(StatusCode.SUCCESS, map);

方案 3:全局配置 Fastjson 保留 null

java 复制代码
JSON.DEFAULT_GENERATE_FEATURE |= SerializerFeature.WriteMapNullValue.getMask();

六、经验教训

  1. 多态坑点 :方法签名是 Object,但实际类型可能是任何实现类,要随时留意运行时类型。

  2. toString 不一定安全 :特别是第三方库的对象,toString() 很可能被重写成带业务逻辑的方法。

  3. 调试时多用 getClass()

    java 复制代码
    System.out.println(result.getResult().getClass());

    一眼就能看出是不是自己想象的类型。


七、总结

这个问题的根因,其实一句话就能说清: JSONObject.toString() 默认会丢掉 null 字段,因为它走的是 Fastjson 的 JSON 序列化逻辑。

但它的难点在于:

  • 表面上你在用 Object.toString(),实则调用了子类的重写方法。
  • 参数是 Object,传进来的到底是什么类型,如果不特意去查,很容易忽略。
  • 调试时看到的对象和最终输出的字符串差异,容易让人把注意力放在 JSON 配置上,而不是类型本身。
相关推荐
野犬寒鸦2 小时前
从零起步学习并发编程 || 第一章:初步认识进程与线程
java·服务器·后端·学习
我爱娃哈哈2 小时前
SpringBoot + Flowable + 自定义节点:可视化工作流引擎,支持请假、报销、审批全场景
java·spring boot·后端
李梨同学丶4 小时前
0201好虫子周刊
后端
思想在飞肢体在追4 小时前
Springboot项目配置Nacos
java·spring boot·后端·nacos
Loo国昌7 小时前
【垂类模型数据工程】第四阶段:高性能 Embedding 实战:从双编码器架构到 InfoNCE 损失函数详解
人工智能·后端·深度学习·自然语言处理·架构·transformer·embedding
ONE_PUNCH_Ge7 小时前
Go 语言泛型
开发语言·后端·golang
良许Linux8 小时前
DSP的选型和应用
后端·stm32·单片机·程序员·嵌入式
不光头强8 小时前
spring boot项目欢迎页设置方式
java·spring boot·后端
怪兽毕设8 小时前
基于SpringBoot的选课调查系统
java·vue.js·spring boot·后端·node.js·选课调查系统
学IT的周星星8 小时前
Spring Boot Web 开发实战:第二天,从零搭个“会卖萌”的小项目
spring boot·后端·tomcat