Skip to content

[BUG] 等价 JSON 数值转 int 时产生拒绝/回绕/饱和三种结果,与 JLS 窄化语义不一致 #7801

Description

@annoymous3

问题描述

同一个数学值 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

重现步骤

  1. 使用 JSON.parseObject(String, Class) 将 JSON 对象绑定到包含 int 字段的 Bean。
  2. 分别输入数学值相同、书写形式不同的 1000000000010000000000.01e10
  3. 可以观察到拒绝、回绕和饱和三种不同结果。
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 写法应产生相同结果,不应因为解析阶段选择了 LongBigDecimalDouble 中间类型而分别出现拒绝、回绕和饱和。

如果按 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
  • 问题在 Stringchar[] 和 UTF-8 byte[] 输入路径均可观察到。
  • 这可能造成校验层和执行层在数值文本被重新格式化后,对同一个业务值产生不同结果。

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions