跳到主要内容
JSON 工具2026-10-11Begin5 分钟阅读

JSON 大整数精度陷阱:雪花 ID 被静默改写时怎么查

症状:ID「差一点点」

联调里一类难查的 bug:

  • 后端日志里订单号 / 用户 ID 是 2099774875233849345
  • 前端、格式化工具或复制进控制台后再看,变成了 2099774875233849400
  • 接口签名、对账、按 ID 回查全部失败,但 JSON.parse 并不报错

这不是网络截断,也不是「格式化改了缩进」。是 IEEE 754 双精度在解析数字字面量时丢了低位。

为什么合法 JSON 也会丢精度

JSON 规范把「数字」定义为十进制字面量,没有规定实现必须用任意精度整数。浏览器与 Node 的 JSON.parse 把数字映射到 JS number(双精度),只有不超过 Number.MAX_SAFE_INTEGER(2^53−1 = 9007199254740991)的整数才能保证逐位还原。

位数(约)例子风险
≤15–16普通自增 ID通常安全
18–19雪花 ID、部分分布式 ID高风险静默舍入
带小数 / 科学计数1e20、0.1另有浮点语义,不在本文焦点

对照实验(在浏览器控制台):

JSON.parse('{"id":2099774875233849345}').id
// → 2099774875233849400
String(JSON.parse('{"id":2099774875233849345}').id) === '2099774875233849345'
// → false
Number.isSafeInteger(2099774875233849345) // → false

雪花一类 ID 把时间戳、机器位、序列号塞进约 64 bit,位数刚好落在 18–19 位危险区——binary64 的尾数(约 53 bit)无法表示每一个整数。自增 ID 若长期停在约 16 位以内通常无事;一旦分布式 ID 越过安全边界,所有未经字符串化的 JS 消费端都会静默改写。

语言对照:谁会舍入

运行时 / 库默认数字类型大整数命运
浏览器 / Node JSON.parseIEEE 754 双精度超过 2^53−1 静默舍入
Python json任意精度 int位数保留
Go encoding/json → float64双精度与 JS 同类陷阱
Go → json.Number / int64文本或 64 位整型类型选对则保留
Jackson → long64 位有符号 long 内通常 OK;若再经 JS 仍会坏

事故常被归因为「只有前端有问题」,其实后端内存里可能一直是正确的 64 位值。中间代理若用双精度再序列化一次,报文在到达浏览器前就已经坏了。每一个调用标准 JSON 解析器的跳点都值得怀疑。

三条出路(按可靠性)

  1. 协议层:ID 用字符串
    {"id":"2099774875233849345"} —— 跨语言最稳,也是多数团队的长期约定。

  2. 调试层:无损解析
    格式化/展示时不要用裸 JSON.parse。本站 JSON 格式化工具 对格式化与压缩走 parseLossless:数字 token 以原文形式保留,再 stringify 回去。适合「先看清报文再改代码」。

  3. 应用层:BigInt / 十进制类型
    仅在你控制两端运行时且明确协议时使用;JSON 文本本身仍常以字符串承载,避免中间代理再 parse 一次。

和站内其他路径的关系

场景风险互链
API 响应里的雪花 ID前端 JSON.parse本文 + 格式化指南
JWT claims 里的大整数解码后再 parseJWT 解码指南
Base64 解出嵌套 JSON编码无损、解析仍可能损Base64 指南·大整数节
两份 JSON Diff 数字对不上可能是解析弄坏而非业务变更排错手册·第四类

实现注记

无损路径用手写递归下降解析器识别数字 token,装进 LosslessNumber{ raw },序列化时打印 raw 而不是 Number(raw)。合法性「校验」若只需 true/false,可以仍用标准 JSON.parse——那只回答「能不能 parse」,不回答「值有没有被改」。签名、哈希、对账场景必须以字符串或无损树为准。

快速自检清单

  1. 把可疑 ID 粘进 JSON 格式化,看输出数字串是否与原文逐位相同。
  2. 若格式化已保全、自己代码里却变了——在业务代码里搜裸 JSON.parse / response.json()。
  3. 推动后端把 ID 字段改为字符串;短期前端可用字符串或 BigInt 策略兜底。
  4. 不要用「再 format 一次」当修复——劣质工具会把错误固化进剪贴板。

排查对照:是精度还是别的问题

你看到的现象更像下一步
ID 末几位变成 0 或整百IEEE 754 舍入本文无损路径 / 改字符串 ID
整段变成 null / 缺字段字段名或序列化配置Schema / Diff
中文变 \uXXXXASCII 转义策略Unicode 转义文
Unexpected token语法排错手册
Diff 显示数字不同,但业务没发版一侧用了有损 parse两侧都走无损或字符串后再比

具体链路:Base64 → JSON → JWT claim

常见流水线:服务 A 把 JSON 做 Base64,服务 B 解出字节再 JSON.parse,某个字段随后写进 JWT payload。Base64 不会改数字字符——损伤发生在 parse。若只在 Base64 工具里看原文,ID 仍正确;任一跳把值落成 JS number,低位就没了。排查顺序:先用无损格式化核对 JSON token → 看 JWT 解码把 claim 显示成字符串还是数字 → 再怀疑传输层。

金额、汇率、库存小数是另一类问题(十进制 vs 二进制浮点),不要和「整数 ID 被改写」混为一谈。ID 用字符串;金额用整数最小货币单位或 decimal 字符串——两条纪律可以同时成立。

FAQ

浮点数也会被无损路径「原样」保留吗?

原文 token(如 0.50、1e5)会按字面保留;这不等于十进制算术精确。金额字段仍应用整数分或 decimal 字符串协议。

这是 JSON 规范的 bug 吗?

规范把数字留给实现。ECMA-404 / RFC 8259 不要求任意精度。问题出在「用双精度承载业务 ID」的工程选择,不是缩进风格。

和「校验通过」矛盾吗?

不矛盾。校验只问语法;精度问语义。语法绿灯 + ID 错位,是本陷阱的标准画像。


本文由 ToolVault 工具匣 提供。更多开发者工具请访问 首页。