alibaba / alibaba/fastjson2

[BUG] fastjson1-compatible 序列化 Map<Integer, Bean> 中的共享 Collection 时生成缺少 Map key 的 $ref

Open
#7,795 1 comment 0 reactions 0 assignees View on GitHub
bug
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

Open the contributing 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.