alibaba / alibaba/fastjson2

[BUG] 等价 JSON 数值转 boolean 时结果依赖书写形式并静默降级

Open
#7,803 0 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

### 问题描述

将 JSON 数值绑定到 Java `boolean` 字段时,fastjson2 的结果取决于数值的**词法书写形式**:整数写法 `1` 得到 `true`,而数学值相同的小数写法 `1.0` 得到 `false`。

RFC 8259 对 JSON number 使用统一的数值语法,并未定义独立的整数和小数数据类型。`1`、`1.0`、`1e0` 和 `10e-1` 表示相同的数学值,因此不应因为解析阶段进入不同的内部数值分支而得到相反的布尔值。

当前行为还存在静默降级:小数数值不会被拒绝,而是无条件得到 `false`。例如 `1.0` 不抛出任何异常,却把原本由整数写法表示为 `true` 的字段静默改为 `false`。

JLS §5.1.7 没有定义数值类型与 `boolean` 之间的装箱、拆箱或转换关系,因此允许数值绑定到 boolean 是 fastjson2 自行定义的 coercion 规则。本问题并非要求 Java 提供这种转换,而是要求库自身的规则满足一致性:同一数学值的不同合法 JSON 写法应得到相同结果。

此外,启用 `JSONReader.Feature.NonZeroNumberCastToBooleanAsTrue` 后,小数路径仍返回 `false`。例如 `0.5` 是非零数值,却没有按照该 Feature 的名称和现有整数语义得到 `true`。

### 环境信息

- OS信息:Ubuntu 20.04.6 LTS
- JDK信息:Eclipse Temurin 1.8.0_402
- 版本信息:Fastjson2 2.0.64;2.0.65-SNAPSHOT

### 重现步骤

1. 使用 `JSON.parseObject(String, Class)` 将 JSON 对象绑定到包含 `boolean` 字段的 Bean。
2. 分别输入数学值相同、书写形式不同的 `1` 和 `1.0`。
3. 可以观察到 `1` 得到 `true`,`1.0` 却得到 `false`,且后者不抛异常。

```java
import com.alibaba.fastjson2.JSON;

public class Repro {
public static class Bean {
private boolean enabled;

public boolean isEnabled() {
return enabled;
}

public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
}

public static void main(String[] args) {
for (String value : new String[]{"1", "1.0", "-1", "2", "0"}) {
Bean bean = JSON.parseObject("{\"enabled\":" + value + "}", Bean.class);
System.out.println(value + " -> " + bean.isEnabled());
}
}
}
```

完整取值表:

| `{"enabled":X}` | 实际结果 | 说明 |
|---|---:|---|
| `1` | `true` | 整数路径 |
| `1.0` | **`false`** | 与 `1` 同值但结果相反 |
| `-1` | `true` | 当前默认规则按绝对值 1 判断 |
| `-2` | `false` | |
| `2` | `false` | |
| `0` | `false` | |
| `0.0` | `false` | |
| `0.5` | `false` | 启用 `NonZeroNumberCastToBooleanAsTrue` 后也仍为 `false` |
| `"1"` | `true` | 字符串路径 |
| `"true"` | `true` | 字符串路径 |
| `"yes"` | `JSONException` | 无法解释的字符串会被拒绝 |
| `null` | `false` | primitive boolean 默认值 |

### 期待的正确结果

在相同的 `JSONReader` 配置下,表示同一数学值的合法 JSON number 写法应产生相同的 boolean 结果,不应由整数、小数或指数的词法形式决定。

为了保持兼容性,可以沿用 fastjson2 当前默认的整数规则,并将其他数值形式与之对齐:

- 默认配置:数值绝对值等于 1 时为 `true`,否则为 `false`;因此 `1`、`1.0`、`1e0`、`10e-1` 均为 `true`,`2` 与 `2.0` 均为 `false`;
- 启用 `NonZeroNumberCastToBooleanAsTrue`:所有非零数值均为 `true`,包括 `0.5`、`-0.5` 和 `2.0`;零的各种写法均为 `false`。

另一种可接受策略是拒绝所有非整数数值并抛出 `JSONException`,但不应继续把无法按当前分支处理的小数静默降级为 `false`。

### 相关日志输出

```text
1 -> true
1.0 -> false
-1 -> true
2 -> false
0 -> false
```

启用 `JSONReader.Feature.NonZeroNumberCastToBooleanAsTrue` 后:

```text
0.5 -> false // 预期为 true
2.0 -> false // 预期为 true
```

#### 附加信息

- 当前整数路径的默认规则实际为 `|value| == 1`,而小数路径无条件返回 `false`,可解释上述全部结果。
- 问题在 `String`、`char[]` 和 UTF-8 `byte[]` 输入路径均可复现。
- 字符串值和 `null` 的现有行为与本问题无关,可以保持不变。
- 同类的词法形式依赖也曾出现在 JSON 数值转 `int` 的路径中:同一数学值可因写法不同而出现拒绝、回绕或饱和。

Contributor guide

Open the contributing guide

Research direction

Start with the JSON.parseObject(String, Class) path and the boolean coercion controlled by JSONReader.Feature.NonZeroNumberCastToBooleanAsTrue. Run the supplied Repro with integer, decimal, exponent, zero, and nonzero values across the described input paths. Done means equivalent JSON number values produce consistent boolean results, with existing string and null behavior preserved.

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
Active
Clarity
Mostly clear
Newbie friendliness
62/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.