JSON 里的大整数 ID 为什么变了?用 JavaScript 复现精度丢失
接口里的订单号或任务 ID 往往很长。如果服务端把 ID 作为 JSON 数字返回,JavaScript 在解析时可能已经改变它的值,即使后面再转成 BigInt 也无法找回原始数字。
下面的例子在 Windows 11、Node.js v22.22.0 上运行,只使用本地数据和 Node.js 内置模块,不访问网络。把代码保存为 json-id-demo.cjs,使用 node json-id-demo.cjs 运行。
'use strict';
const assert = require('node:assert/strict');
const numeric = JSON.parse('{"id":9007199254740993}').id;
const text = JSON.parse('{"id":"9007199254740993"}').id;
console.log('数字 ID:', numeric);
console.log('数字是否为安全整数:', Number.isSafeInteger(numeric));
console.log('字符串 ID:', text);
console.log('数字转 BigInt:', BigInt(numeric).toString());
console.log('字符串转 BigInt 后加 1:', (BigInt(text) + 1n).toString());
assert.equal(String(numeric), '9007199254740992');
assert.equal(Number.isSafeInteger(numeric), false);
assert.equal(text, '9007199254740993');
assert.equal(BigInt(numeric).toString(), '9007199254740992');
assert.equal((BigInt(text) + 1n).toString(), '9007199254740994');
try {
JSON.stringify({id: BigInt(text)});
throw new Error('预期 BigInt 序列化失败');
} catch (error) {
assert.equal(error.name, 'TypeError');
console.log('直接序列化 BigInt:', error.name);
}
console.log('字符串形式序列化:', JSON.stringify({id: BigInt(text).toString()}));
console.log('检查通过');
本机输出:
数字 ID: 9007199254740992
数字是否为安全整数: false
字符串 ID: 9007199254740993
数字转 BigInt: 9007199254740992
字符串转 BigInt 后加 1: 9007199254740994
直接序列化 BigInt: TypeError
字符串形式序列化: {"id":"9007199254740993"}
检查通过
JavaScript 的 Number 使用双精度浮点数。安全整数范围是 -(2^53 - 1) 到 2^53 - 1,在这个范围内整数可以精确表示并与相邻整数区分。示例中的数字超过该范围,解析成 Number 后已经变成另一个值;再调用 BigInt(numeric) 只能转换已经舍入的结果。Number.isSafeInteger 返回 false 表示不能把该值当作安全整数,并不意味着每一个超出范围的整数都无法精确表示。
对只用于标识、不参与计算的 ID,接口双方可以约定使用 JSON 字符串,并一直按字符串保存和传递。确实需要整数运算时,可以先从原始字符串创建 BigInt,运算后再转回字符串发送;JSON.stringify 不能直接序列化 BigInt。如果服务端已经返回 JSON 数字且客户端无法调整接口,应在普通 Number 解析丢失精度之前,使用能保留原始数字文本或大整数的解析方式;对已经舍入的 Number 做类型转换不能修复数据。
JSON 本身的数字语法并不强制所有实现采用 JavaScript 的 Number,但跨语言交换时应考虑接收端的精度限制。可参考 RFC 8259 第 6 节及 JavaScript 的 Number.isSafeInteger 文档。
本文借助 AI 辅助整理,示例已在上述环境运行核验。
- 点赞
- 收藏
- 关注作者
评论(0)