[BUG] fastjson1-compatible 序列化 Map<Integer, Bean> 中的共享 Collection 时生成缺少 Map key 的 $ref
- Dominant language
- Java
- Stars
- 4.4k
- Forks
- 613
- Avg merge
- 1d 22h
- Merged PRs (30d)
- 6
Description
### 问题描述
从 Fastjson 1.2.83 升级到 `fastjson1-compatible 2.0.63` 后,当根对象为 `Map`,且多个 Bean 持有同一个 Collection 实例时,`JSON.toJSONString()` 会为后续 Collection 生成错误的 `$ref` 路径。
实际生成的引用:
```json
{
"$ref": "$.ids"
}
```
该路径缺少根 Map 的 key。第一次出现的集合位于 `1001 -> ids`,如果需要输出引用,路径应该类似:
```json
{
"$ref": "$[1001].ids"
}
```
使用 fastjson1-compatible 2.0.63 反序列化错误 `$ref` 时不会抛出异常,而是将后续 Bean 的 `ids` 字段静默设置为 `null`。
这里不是循环引用,而是多个 Bean 共享同一个 Collection 实例。
### 环境信息
- OS信息:macOS
- JDK信息:OpenJDK 17.0.16
- 版本信息:`com.alibaba:fastjson:2.0.63`(fastjson1-compatible)
### 重现步骤
运行以下代码:
```java
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
public class FastjsonSharedCollectionRefTest {
public static void main(String[] args) {
Set sharedEmptySet = Collections.emptySet();
Map sourceModels = new LinkedHashMap<>();
sourceModels.put(1001, new Model(sharedEmptySet));
sourceModels.put(1002, new Model(sharedEmptySet));
System.out.println(
"Shared before serialization: "
+ (sourceModels.get(1001).getIds()
== sourceModels.get(1002).getIds())
);
String json = JSON.toJSONString(sourceModels);
System.out.println("Serialized JSON: " + json);
Map parsedModels = JSON.parseObject(
json,
new TypeReference>() {
}
);
System.out.println("1001.ids: " + parsedModels.get(1001).getIds());
System.out.println("1002.ids: " + parsedModels.get(1002).getIds());
}
public static class Model {
private Set ids;
public Model() {
}
public Model(Set ids) {
this.ids = ids;
}
public Set getIds() {
return ids;
}
public void setIds(Set ids) {
this.ids = ids;
}
}
}
```
序列化结果中,第二个 `ids` 类似:
```json
{
"$ref": "$.ids"
}
```
反序列化结果:
```text
Shared before serialization: true
1001.ids: []
1002.ids: null
```
### 期待的正确结果
兼容 Fastjson 1.2.83 的行为,两个集合均输出实际内容:
```json
{
"1001": {
"ids": []
},
"1002": {
"ids": []
}
}
```
或者,如果 fastjson1-compatible 需要使用引用检测,则 `$ref` 应包含第一次出现该集合的完整路径:
```json
{
"$ref": "$[1001].ids"
}
```
反序列化后至少应满足:
```text
1001.ids: []
1002.ids: []
```
无法解析 `$ref` 时也不应静默返回 `null`,建议抛出明确的反序列化异常。
### 相关日志输出
没有异常日志。问题表现为反序列化成功,但第二个共享 Collection 字段被静默设置为 `null`。
错误引用:
```json
{
"$ref": "$.ids"
}
```
该路径从根 `Map` 无法定位到第一次出现的集合,因为缺少 `1001` 这一层。
### 附加信息
使用以下方式关闭引用检测后可以正常工作:
```java
import com.alibaba.fastjson.serializer.SerializerFeature;
String json = JSON.toJSONString(
sourceModels,
SerializerFeature.DisableCircularReferenceDetect
);
```
此时两个 `ids` 均输出为 `[]`,反序列化后也不会变成 `null`。
相似问题:
- https://github.com/alibaba/fastjson2/issues/3516
- https://github.com/alibaba/fastjson2/issues/7793
- https://github.com/alibaba/fastjson2/issues/956
希望确认:
1. fastjson1-compatible 为共享 Collection 生成 `$ref` 是否属于预期兼容行为。
2. `$ref` 丢失外层 Map key 是否属于 writer bug。
3. 无法解析 `$ref` 时静默将字段设置为 `null` 是否可以改为抛出异常。
Contributor guide
Research direction
Start by reproducing the case through JSON.toJSONString and JSON.parseObject with the shared Set and Integer-keyed Map shown in the issue. Trace reference-path creation for the second ids field and the compatible deserializer's handling of the resulting $ref. Done means both ids fields remain non-null after parsing, with a complete reference path or equivalent correct output, and invalid references are not silently converted to null.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- java
- Domain
- backend
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 64/100