JSON 工具箱

大整数为什么变成 9007199254740992

结论:超过 2^53-1 的整数在 IEEE 754 双精度里无法精确表示,JSON.parse 会静默把末位改掉。订单号、雪花 ID、金额分单位都踩过这个坑,正确解法是全程当字符串传。

进阶约 4 分钟发布于 2026-10-30更新于 2026-11-04
大整数在 JSON 解析中精度丢失的原因与解法

结论:把雪花 ID、订单号、bigint 主键这类标识符当作字符串,从序列化、传输到解析全程不进入数字通道;只有在确认字段不会超过 2^53-1 时,才可以保留数字类型并在 Schema 里加 maximum 约束。

因为解析它的那一刻,精度就已经丢了,而且不会报错。

JSON.parse('{"id": 9007199254740993}');
// { id: 9007199254740992 }

少的那一位,找不回来了。

2^53 这条线怎么来的

JavaScript 的 number 只有一种:IEEE 754 双精度浮点。它用 53 位表示有效数字(1 位隐含 + 52 位显式),所以能精确表示的整数范围是:

-9007199254740991 ~ 9007199254740991   (即 ±(2^53-1))

超过这条线,相邻两个整数之间的距离不再等于 1,而是 2、4、8…… 具体能落到哪个值取决于二进制表示。

9007199254740993 的二进制末位是 1,而在这个量级上能被表示的数末位都是 0,于是它只能进位到 9007199254740992。

哪些字段最容易中招

字段 典型位数 风险
雪花算法 ID 18–19 位 高,末几位直接错
订单号、流水号 16–20 位 高
数据库 bigint 主键 19 位 高
金额(以分为单位) 一般安全 低,但换成毫、微就可能超
时间戳(毫秒) 13 位 安全,能撑到公元 28 万年
身份证号 18 位 高,且首位可能被吃掉

共同点:它们是标识符,不是用来做算术的数字。标识符不需要加减乘除,只需要原样搬运,所以最合适的类型是字符串。

四种解法

解法一:序列化时就转字符串。 后端在出参前把 bigint 字段转成 string,前端按字符串处理,比较、拼接都不受影响。这是最省事也最可靠的做法,代价是要在接口文档里写清楚「这些字段是字符串」。

解法二:让序列化库处理。 Java 的 Jackson 可以给 Long 类型配 ToStringSerializer;Python 把 int 转 str 再输出;Go 用 json:",string" 标签。核心都是「不让它进数字通道」。

解法三:前端用 BigInt 解析。 JSON.parse 本身不支持,需要 json-bigint 之类的库把大整数解析成 BigInt。注意 BigInt 不能和 Number 混算,JSON.stringify 也不认它,后续处理要配套改。

解法四:约定安全范围。 如果字段确实不会超过 2^53,在 Schema 里加 maximum 约束,让越界数据在校验阶段就失败,而不是等着它在浏览器里被改掉。

怎么验证自己有没有踩坑

拿一个已知的 ID,走一遍「序列化 → 传输 → 解析」,比对前后是否逐字相等。用 JSON 格式化 粘贴原始报文时,注意看数字有没有被高亮成普通数字——超过 16 位的整数就该警觉。

更彻底的办法是抓包看原始响应体:如果响应里是 "id": 1234567890123456789(没有引号),而前端拿到的末位不对,基本可以确定是解析阶段丢的精度。

常见问题

为什么 JSON.parse 改了末位却不报错?

JavaScript 的 number 只有 IEEE 754 双精度一种类型,超出 2^53-1 的整数只能落到附近的近似值上。JSON.parse 把这一步当成正常的数值转换,既不抛错也不提示。

前端能否用 BigInt 保住大整数?

可以,但 JSON.parse 本身不支持,要换成 json-bigint 这类库。代价是 BigInt 不能和 Number 混算,JSON.stringify 也不认它,后续处理要跟着改。

要不要在接口文档里标明这些字段是字符串?

要。序列化时转字符串最省事也最可靠,但前提是消费方能按字符串处理,所以字段类型必须在接口文档里写死,否则前端仍会把它当数字用。

延伸阅读