Skip to content

Fix issue #3293 and issue #3328 #3384

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 0 commits into from
Closed

Fix issue #3293 and issue #3328 #3384

wants to merge 0 commits into from

Conversation

CodePlayer
Copy link
Contributor

@CodePlayer CodePlayer commented Mar 12, 2025

What this PR does / why we need it?

之前 Fastjson 2.0.53 是可以将非字符串形式的自动 toString(), 后来 Fastjson 2.0.54 调整之后,就不支持了。
我看了下,这次调整是为了修复 Issue #3264

如果只需要支持字符串形式的 key 的话,那么 #3264 其实也是无需修复的。
这次调整后,就导致部分key输出会附带额外的 单引号 或 双引号。

虽然 Map 具有非字符串形式 的 key 并不符合严格的 JSON 规范,但是在实际开发过程中,非字符串形式的 key 还是客观且普遍存在的。

比如:

  • EnumMap 的 key 是一个枚举,这是一种比较常见的用法,如果要转换为字符串,就需要将所有数据额外转换并迁移到其他 Map 类型。
  • TreeMap<Integer, Object> 的 key 是一个整型,整型的排序规则和字符串也是不一样的,所以要保持相同的排序规则,也需要额外再多作一次转换,并且还不能用错 Map 类型,否则会破坏业务排序。

Summary of your change

修复 bug #3293 和 bug #3328

实现思路是:

  1. 为了让有 Filter 时 和 无 Filter 的表现一致,最好的方式是两边共用相同的 isWriteAsString 判断逻辑 和 writeName 写入处理代码。
  2. 但是 writeName 的代码是分散在多个类的多个方法里面,且夹杂其他与 name 无关的业务逻辑,无法统一抽取封装,难以实现全部共用。
  3. 对于 nullStringIntegerLong 等常见类型,可直接复用。
  4. 设置了 WriteNonStringKeyAsStringBrowserCompatible 等 Feature,且属于内置简单类型的,直接 key.toString()
  5. 对于其他自定义类型(或没有设置上述 Feature 的),仍然调用之前的 JSON.toJSONString(),如果返回的字符串两侧带了引号的,就会被手动去掉。实际上自定义类型序列化的结果无非就是三种:数组[]、对象{} 和 字符串"",目前主要还是 Enum 和 时间类型,对应的JSON输出会被额外加引号。

这只是第一版的实现方案,先确保加了 WriteNonStringKeyAsStringBrowserCompatible Feature 的,还是可以正常使用
毕竟,最常见的一般就只有 字符串、整型、枚举。只要恢复对这些类型的支持,就满足了绝大多数场景下的需求。

至于上面第5步,可能会多耗费一点性能。但和之前相比,也只有在两侧多了引号时才会增加性能开销,并且这种场景在前端输出时非常少见。

此外,我也已经完成了第二版实现,着重优化第5点的性能,能够实现无需调用 JSON.toJSONString() 和 去除引号 的额外开销。
第二版也已经通过了单元测试。不过有一个细节我还要再斟酌一下,需要覆盖更多场景,所以暂时先提交第一版。

Please indicate you've done the following:

  • Made sure tests are passing and test coverage is added if needed.
  • Made sure commit message follow the rule of Conventional Commits specification.
  • Considered the docs impact and opened a new docs issue or PR with docs changes if needed.

@wenshao
Copy link
Member

wenshao commented Mar 13, 2025

两个issue的问题不是一个,请分开两个PR提交

@CodePlayer
Copy link
Contributor Author

CodePlayer commented Mar 13, 2025

我看了下,其实就是一个问题呀,当序列化设置了 Filter 时,底层除了 String 类型,其他简单类型的 Map key 都被 JSON.toJSONString() 了,再拿去作为 key 都会多一个双引号。

反正我是尝试修复了常用类型作为Map key会有多余的引号问题,我也只修复了这一个哦。

@CodePlayer CodePlayer closed this Mar 13, 2025
@CodePlayer
Copy link
Contributor Author

CodePlayer commented Mar 13, 2025

刚刚发现代码里面多了个 System.out.println 调试语句忘记删除,编辑历史提交后,又疑似因为 git 本身的 bug(或者存在 pull request ?)强制覆盖也无法生效。所以只好将之前的仓库直接删除,然后重新 fork 了。

麻烦你确认一下,这两个 Issue 应该是同一个问题的。
确认后,我再发起 PR 吧。

@CodePlayer
Copy link
Contributor Author

CodePlayer commented Mar 13, 2025

BTW,我看 ObjectWriter1 ~ ObjectWriter12 的代码高度重复,除了多了几个 fieldWriter* 属性,似乎直接用一个数组来合并封装会更好 ?

难道是想优化循环调用,比如将 for 1...100 { doSomething() } 改进为 for 1...10 { doSomething1(); doSomething2(); ... doSomething10(); }

但感觉只要不将 fieldWriters 数组放入 Arrays.asList( ) ,再一个个 get(index) 分散为字段,这里节省的开销,就应该超过直接使用数组循环的开销了吧 ?

还是说,这一块是有什么特殊考虑呢 ?

@wenshao
Copy link
Member

wenshao commented Mar 13, 2025

这个设计会减少首次序列化的时间,因为fieldWriterN是重用了父类的。

@CodePlayer
Copy link
Contributor Author

这个设计会减少首次序列化的时间,因为fieldWriterN是重用了父类的。

如果把前面的重复代码,抽取成公共方法,保留后面的差异代码,例如:

fieldWriter0.write(jsonWriter, object);
fieldWriter1.write(jsonWriter, object);
fieldWriter2.write(jsonWriter, object);
fieldWriter3.write(jsonWriter, object);
fieldWriter4.write(jsonWriter, object);
// ......

会有影响吗?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants