大家好,我是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 的序列化逻辑。
四、真相揭秘
-
Fastjson 的 JSONObject 重写了 toString()
java@Override public String toString() { return JSON.toJSONString(this); }
-
Fastjson 默认过滤 null
javaJSON.toJSONString(obj); // 默认不输出 null
-
所以当你调用:
javaresult.getResult().toString();
实际等价于:
javaJSON.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();
六、经验教训
-
多态坑点 :方法签名是
Object
,但实际类型可能是任何实现类,要随时留意运行时类型。 -
toString 不一定安全 :特别是第三方库的对象,
toString()
很可能被重写成带业务逻辑的方法。 -
调试时多用 getClass():
javaSystem.out.println(result.getResult().getClass());
一眼就能看出是不是自己想象的类型。
七、总结
这个问题的根因,其实一句话就能说清: JSONObject.toString()
默认会丢掉 null 字段,因为它走的是 Fastjson 的 JSON 序列化逻辑。
但它的难点在于:
- 表面上你在用
Object.toString()
,实则调用了子类的重写方法。 - 参数是
Object
,传进来的到底是什么类型,如果不特意去查,很容易忽略。 - 调试时看到的对象和最终输出的字符串差异,容易让人把注意力放在 JSON 配置上,而不是类型本身。