问题描述
同一个数学值 10¹⁰ 绑定到 Java int 字段时,fastjson2 的结果取决于 JSON 数值的词法书写形式,而不是数值本身:
- 整数写法
10000000000:抛出 JSONException;
- 普通小数写法
10000000000.0:得到 1410065408,发生模 2^32 回绕;
- 科学计数法
1e10:得到 2147483647,发生饱和。
这三种 JSON token 表示完全相同的数学值,却触发了三条不同的内部转换路径。
其中科学计数法的结果与 JLS §5.1.3 规定的浮点数到整数窄化转换一致:超出 int 正向范围时应饱和到 Integer.MAX_VALUE,即:
(int) 1e10 == Integer.MAX_VALUE;
普通小数路径表现得像 BigDecimal.intValue(),而整数路径执行了显式溢出拒绝。当前行为把内部中间类型和转换实现泄漏到了公开 JSON 绑定结果中,导致等价数值无法获得等价结果。
环境信息
- OS信息:Ubuntu 20.04.6 LTS
- JDK信息:Eclipse Temurin 1.8.0_402
- 版本信息:Fastjson2 2.0.64;2.0.65-SNAPSHOT
重现步骤
- 使用
JSON.parseObject(String, Class) 将 JSON 对象绑定到包含 int 字段的 Bean。
- 分别输入数学值相同、书写形式不同的
10000000000、10000000000.0 和 1e10。
- 可以观察到拒绝、回绕和饱和三种不同结果。
import com.alibaba.fastjson2.JSON;
public class Repro {
public static class Bean {
private int count;
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
}
public static void main(String[] args) {
for (String value : new String[]{"10000000000", "10000000000.0", "1e10"}) {
try {
Bean bean = JSON.parseObject("{\"count\":" + value + "}", Bean.class);
System.out.println(value + " -> " + bean.getCount());
} catch (Exception error) {
System.out.println(value + " -> " + error.getClass().getSimpleName());
}
}
}
}
完整写法矩阵如下,所有输入值均为 10¹⁰:
| 写法 |
实际结果 |
转换表现 |
10000000000 |
JSONException |
拒绝 |
10000000000.0 |
1410065408 |
回绕 |
10000000000.00 |
1410065408 |
回绕 |
1e10 |
2147483647 |
JLS 饱和 |
1E10 |
2147483647 |
JLS 饱和 |
1.0e10 |
2147483647 |
JLS 饱和 |
100e8 |
2147483647 |
JLS 饱和 |
0.1e11 |
2147483647 |
JLS 饱和 |
期待的正确结果
在相同的 JSONReader 配置下,表示同一数学值的不同合法 JSON 写法应产生相同结果,不应因为解析阶段选择了 Long、BigDecimal 或 Double 中间类型而分别出现拒绝、回绕和饱和。
如果按 Java 原始类型窄化语义处理,JLS §5.1.3 给出的参考结果是饱和到 Integer.MAX_VALUE。如果 fastjson2 选择维持其已记录的默认溢出策略,则应至少保证:
- 默认配置下,所有超出
int 范围的等价写法统一抛出 JSONException;
- 启用
JSONReader.Feature.NonErrorOnNumberOverflow 后,所有等价写法统一采用相同的非报错窄化结果。
无论选择哪种策略,都不应由数值的词法书写形式决定结果。
相关日志输出
10000000000 -> JSONException
10000000000.0 -> 1410065408
1e10 -> 2147483647
参考 Java 转换结果:
(int) 1e10 // 2147483647,JLS §5.1.3 饱和
new BigDecimal("1e10").intValue() // 1410065408,模 2^32 回绕
附加信息
long 字段不受影响,上述八种写法均得到 10000000000L。
- 问题在
String、char[] 和 UTF-8 byte[] 输入路径均可观察到。
- 这可能造成校验层和执行层在数值文本被重新格式化后,对同一个业务值产生不同结果。
问题描述
同一个数学值
10¹⁰绑定到 Javaint字段时,fastjson2 的结果取决于 JSON 数值的词法书写形式,而不是数值本身:10000000000:抛出JSONException;10000000000.0:得到1410065408,发生模2^32回绕;1e10:得到2147483647,发生饱和。这三种 JSON token 表示完全相同的数学值,却触发了三条不同的内部转换路径。
其中科学计数法的结果与 JLS §5.1.3 规定的浮点数到整数窄化转换一致:超出
int正向范围时应饱和到Integer.MAX_VALUE,即:普通小数路径表现得像
BigDecimal.intValue(),而整数路径执行了显式溢出拒绝。当前行为把内部中间类型和转换实现泄漏到了公开 JSON 绑定结果中,导致等价数值无法获得等价结果。环境信息
重现步骤
JSON.parseObject(String, Class)将 JSON 对象绑定到包含int字段的 Bean。10000000000、10000000000.0和1e10。完整写法矩阵如下,所有输入值均为
10¹⁰:10000000000JSONException10000000000.0141006540810000000000.0014100654081e1021474836471E1021474836471.0e102147483647100e821474836470.1e112147483647期待的正确结果
在相同的
JSONReader配置下,表示同一数学值的不同合法 JSON 写法应产生相同结果,不应因为解析阶段选择了Long、BigDecimal或Double中间类型而分别出现拒绝、回绕和饱和。如果按 Java 原始类型窄化语义处理,JLS §5.1.3 给出的参考结果是饱和到
Integer.MAX_VALUE。如果 fastjson2 选择维持其已记录的默认溢出策略,则应至少保证:int范围的等价写法统一抛出JSONException;JSONReader.Feature.NonErrorOnNumberOverflow后,所有等价写法统一采用相同的非报错窄化结果。无论选择哪种策略,都不应由数值的词法书写形式决定结果。
相关日志输出
参考 Java 转换结果:
附加信息
long字段不受影响,上述八种写法均得到10000000000L。String、char[]和 UTF-8byte[]输入路径均可观察到。