JSON 大整数精度丢失:如何定位和避免
订单号最后一位变了,但 JSON 校验没有报错?先比较原始响应和解析后的值。语法有效与数字能否被消费端精确表示,是两个不同的检查。下面用一个可复现的例子定位问题。
打开工具:JSON 解析与格式化1. 用原始文本复现问题
下面的代码直接在 JavaScript 中解析一段 JSON。检查 id 时,原文中的 9007199254740993 已经变成 9007199254740992;之后再格式化,无法把丢掉的数字找回来。
const raw = '{"id":9007199254740993}';
const parsed = JSON.parse(raw);
console.log(parsed.id); // 9007199254740992
console.log(JSON.stringify(parsed));
// {"id":9007199254740992}2. 确认是数字表示问题,不是 JSON 语法问题
JavaScript Number 的最大安全整数是 9007199254740991,即 2 的 53 次方减 1。超出安全整数范围后,不能保证每个整数都能被准确表示和区分。这不等于所有更大的数字都必然被改变。
JSON 文本可以包含这样的数字。限制出现在消费端如何解析和处理它。DevDesk 的 JSON 格式化与压缩保留数字原文,因此可用于检查原始响应;它不会替其他系统改变数字类型。
3. 沿数据流找出第一次变化的位置
不要只比较控制台里已经解析好的对象。保存收到的原始响应文本,与后台日志或数据源的原始标识符对照,再逐步检查解析和输出。
- 把原始响应粘贴到 JSON 工具,格式化后检查末尾几位。
- 用文本对比工具比较原始响应与应用重新输出的 JSON。
- 如果响应原文已经错误,回到服务端序列化或数据来源排查。
- 如果只有解析后错误,检查是否经过 Number、parseInt 或默认 JSON.parse。
4. 标识符用字符串,运算按需求处理
如果字段只用来标识订单、用户或记录,可以在接口契约中把它定义为字符串,并在序列化前从准确的来源生成字符串。给已经被舍入的 Number 加引号仍然会保留错误结果。
需要整数运算时,可以从完整的字符串创建 BigInt。普通 JSON.stringify 不会直接序列化 BigInt;传输时需与接收端约定字符串或其他明确的编码方式。
const payload = { id: "9007199254740993" };
const text = JSON.stringify(payload);
const received = JSON.parse(text);
console.log(received.id); // "9007199254740993"
const exactInteger = BigInt(received.id);
console.log(exactInteger.toString()); // "9007199254740993"5. 检查每一种实际转换
重新检查客户端、日志、导出和存储中的字段类型。保留 JSON 原文的格式化工具不代表所有转换都无损:本站 YAML ↔ JSON 转换会拒绝超出安全范围的整数,而不是静默输出被舍入的值。
验证时同时使用普通整数、9007199254740991 和 9007199254740993,检查类型、文本和应用行为。只有符合接口约定的结果才算修复完成。