alibaba / alibaba/fastjson2

[BUG] 设置 ValueFilter 后,枚举类型的 Map.key 输出会无故增加多余的双引号

Open
#3,293 2 comments 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

### 问题描述

如以下代码所示,一个使用枚举值作为Key的Map集合,使用 Fastjson 2.0.54 进行两次JSON输出(具体输出内容参见最后两行代码的注释):
1. 第一次没有设置 `valueFilter` ,输出就是正常的。
2. 第二次只是多设置了一个 `valueFilter`,输出的 Map.key 就会额外多带一个双引号。

### 环境信息
*请填写以下信息:*

- OS信息: Windows 11 64bit
- JDK信息: OpenJDK 8、17
- 版本信息:Fastjson2 2.0.54( **2.0.53 就没有这个问题** )

### 重现步骤
*如何操作可以重现该问题:*
```java
public interface ValueEnum {

/** 获取枚举的 value 值 */
V getValue();

}

public enum MemberType implements ValueEnum {
USER(1),
ADMIN(2),
AGENT(3);

final Integer value;

MemberType(Integer value) {
this.value = value;
}

@Override
public Integer getValue() {
return value;
}

}

@Test
public void test() {
final ValueFilter valueFilter = (object, name, value) -> {
if (value instanceof Enum) {
if (value instanceof ValueEnum) {
return ((ValueEnum) value).getValue();
}
return ((Enum) value).ordinal();
}
return value;
};
Map memberCountMap = new HashMap<>(4);
memberCountMap.put(MemberType.ADMIN, 1);
memberCountMap.put(MemberType.USER, 123);

System.out.println(JSON.toJSONString(memberCountMap)); // {"USER":123,"ADMIN":1}
System.out.println(JSON.toJSONString(memberCountMap, valueFilter)); // {"\"USER\"":123,"\"ADMIN\"":1}
}
```

#### 附加信息
【**顺便探讨一下**】

前两天,我看到有人反馈了一个 BUG #3283 , 说是无法解析单引号形式的 `'true'` 为 `boolean` 类型。
我顺便看了一下 `readBoolValue()` 的源代码,里面似乎只有双引号相关的判断:
https://github.com/alibaba/fastjson2/blob/714a2b6ff235709d4568cf4fb88e41ee767f11eb/core/src/main/java/com/alibaba/fastjson2/JSONReaderUTF8.java#L7124-L7153

不过在 `readInt32Value()` 和 `readInt64Value()` 等方法里面,又有单引号相关的判断。

此外,我自己也算是 Fastjson 的资深用户了,使用过程中也或多或少遇到过一些表现不一致的问题,有些可能是出于性能考虑设计如此,有些感觉就是部分逻辑判断太过分散导致的。

我举个例子:判断一个值是否为 `null`,这样的代码有好几十处,我在代码的不同地方看到了至少3种不同形式的判断:
形式一:
```java
// 判断值是否为 null
str.isEmpty() || "null".equals(str)
```
形式二:
```java
// 判断值是否为 null
str.isEmpty() || "null".equals(str) || "NULL".equals(str)
```

形式三:
```java
str.isEmpty() || "null".equalsIgnoreCase(str)
```

判断一个值是否为 `null`,虽然代码只有一行,但因为许多地方的代码都是分开写的,这就导致判断是否为 `null` 的逻辑**并不统一**,导致输出的结果也可能多种多样。
而且,如果后续要**统一调整或扩展**也比较麻烦,一旦改漏一个地方,就可能导致意外发生。

我建议,将类似这样影响输出结果的逻辑判断,都尽量封装为统一的方法,比如可以在` TypeUtils` 中定义如下方法:
```java
public static boolean considerAsNull(@Nullable String str) {
return str == null || str.isEmpty() || "null".equals(str) || "NULL".equals(str);
}
```
1. 上面所有涉及到值为 `null` 的判断都统一调用该方法。
2. 如果确有特殊情况,可以另起一个方法,并用注释进行特别说明。

我在本地已经在尝试这样去重构,但是,多种代码的逻辑不一样,如果要统一的话,我不清楚你最终会选择哪一种。
有些疑似 bug 的我也不太清楚是代码疏漏,还是有特殊考虑、设计如此,也需要确认,所以也不好擅自下手。

此外,有些代码,通过代码分析工具也能够发现一些问题,比如分支条件必定为 `true/false`、分支条件不可达、变量定义了未使用、代码重复。
这种地方,潜藏 bug 的可能性也就相对比较高。

Contributor guide

Open the contributing guide

Research direction

Run the provided JSON.toJSONString reproducer with and without ValueFilter, then trace the Map key serialization path and ValueFilter handling. Add a regression test for enum keys implementing ValueEnum; done means filtered output contains USER and ADMIN as normal keys without extra escaped double quotes.

Written by the indexing model from the issue text.

Assessment

Tech stack
java
Domain
backend
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.