我解决了 FastJson Issue:空对象序列化为空数组,应该怎么写工作很多年,一直CRUD,实在没意思,也没前途。
工作很多年,一直CRUD,实在没意思,也没前途。最近得一大佬指点,开始参与开源项目,从解决 Issue 开始。
这不,真的逮到机会了。有人在 FastJson 的 Github 主页上面提交了一个需求,想把空对象序列化为空数组,应该怎么写?
具体的需求如下图:
我第一反应是,这都是什么鬼问题啊。不过,冷静下来想,一般开发遇到问题,都是问搜索引擎和AI。
能跑到 Github 上面来提问题,首先说明,这个程序员,有参与开源社区的习惯,对开源社区的运作机制也有一定的理解,这一点值得赞赏。其次说明,这个问题,恐怕没有那么简单。
问题分析
我仔细分析了一下这个问题,结合以往的开发经验,很快就意识到这个需求可能需要实现一个自定义的 ObjectReader。
然而,当我真正开始着手实施时,很快就遇到了一个不小的挑战。ObjectReader 的实现远比想象中复杂,它并不遵循常规的编程模式。这种复杂性主要源于 FastJson 在设计上对性能的极致追求,导致了一些非常规的实现方式。
我曾看过 Jackson 的源码,其反序列化逻辑相对直观:它首先将 JSON 字符串解析成一个 JsonNode 树状结构,然后再将这个树转化为 Java 对象。这种两步走的方法虽然可能在性能上有所损失,但大大简化了自定义序列化和反序列化的实现。
相比之下,FastJson 采用了一种更为直接的方法。它没有中间的树状结构这一步骤,而是直接从 JSON 字符串到 Java 对象。这种设计虽然提高了性能,但也使得自定义 ObjectReader 的实现变得更加复杂。
这让我有些打退堂鼓!
好在这时候,大佬指导我说,在源码里找找近似实现,然后再去改改。
参考实现
我找了找,果然找到了一个 ObjectReaderImplListStr,这个实现是序列化 String 数组。在里面有这样的 If-else 操作。
char ch = jsonReader.current();
if (ch == '[') {
jsonReader.next();
while (!jsonReader.nextIfArrayEnd()) {
String item = jsonReader.readString();
if (item == null && list instanceof SortedSet) {
continue;
}
list.add(item);
}
} else if (ch == '"' || ch == '\'' || ch == '{') {
String str = jsonReader.readString();
if (str != null && !str.isEmpty()) {
list.add(str);
}
}
如果发现当前字符不是’[’,则把余下的内容当成一个字符串。
这个需求,与原 ISSUE 提交者的需求非常接近了,我只改了一行代码,把 list.add 注释掉,因为原ISSUE 需要返回的是空数组。
else if (ch == '"' || ch == '\'' || ch == '{') {
String str = jsonReader.readString();
if (str != null && !str.isEmpty()) {
//list.add(str);
}
}
于是,我写了一个 MyListReader,并使用注解 JSONField 添加到了字段上:
@JSONField(deserializeUsing = MyListReader.class)
private List<Integer> list;
一切准备就绪,开始运行,可是结果却不符合预期!
Range{start=2, end=1, list=[{}]}
很显然,预期返回 list=[], 但 FastJson 把 ”{}” 当成一个字符串返回了,MyListReader 并没有被用上!
这是为啥呢?
大佬丢给我一个走读源码的工具,XCodeMap,让我趁此机会,走读一下代码。
走读源码
XCodeMap 确实是一个走读代码的神器,它会把程序走过的函数给高亮出来,一眼看出分支走向,快速略过无关的分支,并通过查看定义,直接找到当前的实现。
由于免除了分支抽象的困扰,我很快找到了一行关键代码,所有基于 field 的 FieldWriter 都被忽略了。
再利用 XCodeMap 的对象追踪能力,很快可以查到 fieldInfo 设置成ignore是由于以下两行代码:
fieldInfo.init();
fieldInfo.ignore = (field.getModifiers() & Modifier.PUBLIC) == 0 || (field.getModifiers() & Modifier.TRANSIENT) != 0;
看代码,应该就猜得出来,“非 public” 或者 “transient” 的字段都会被忽略。
也就是说,我设置的 MyListReader 被忽略了。
于是,我把字段属性改成 public,再运行一次,果然OK了。
解决办法
终极解决办法,当然不是把字段改成 public,而是把注释加到方法上面,类似于下面这样:
@JSONField(deserializeUsing = MyListReader.class)
public void setList(List<String> list) {
this.list = list;
}
问题圆满解决。最终,我把答案贴到了 Issue 里面,原ISSUE 作者,也给我表达了感谢。
这还是第一次,用自己的技术在开源社区,帮助到别人,真的是非常开心。
经历总结
参与开源项目,确实是一个很好的学习机会。这次经历也让我体会到了解决复杂问题的过程。从最初的困惑到最终的解决,我经历了问题分析、寻找参考实现、源码走读等多个阶段。这个过程不仅锻炼了我的技术能力,还提升了我的问题解决能力和耐心。
这次经历也让我认识到了工具的重要性。无论是 Github 这样的开源平台,还是 XCodeMap 这样的源码阅读工具,都极大地提高了我的工作效率。在今后的工作中,我会更加注重工具的选择和使用,以此来提升自己的工作效率和代码质量。
参考:
转载自:https://juejin.cn/post/7423706686173003810