先记住这三点
- JavaScript 安全整数范围是 -(2^53-1) 到 2^53-1,即 Number.MAX_SAFE_INTEGER。
- 大整数一旦被 JSON.parse 转成 Number,精度可能已经丢失,之后再转 BigInt 无法恢复原值。
- 跨语言 API 中的订单号、用户 ID 和雪花 ID,最稳妥的方式通常是按字符串传输。
精度为什么会丢失
JSON 标准只定义 number,没有规定具体使用 32 位、64 位整数还是双精度浮点数。Java、Go、数据库和 JavaScript 对数字的表示方式不同,同一段 JSON 在不同语言中可能得到不同的精度。
JavaScript 的 Number 使用 IEEE 754 双精度浮点数。超过 9007199254740991 后,并非每个整数都能被单独表示,相邻的两个大整数可能映射到同一个 Number。此时比较、显示或重新序列化都可能出现错误。
Number.MAX_SAFE_INTEGER
// 9007199254740991
9007199254740992 === 9007199254740993
// true为什么 JSON.parse 之后再转 BigInt 已经太晚
JSON.parse 会先把数字文本转换成 Number。如果原始值超出安全整数范围,这一步就可能发生舍入。随后执行 BigInt(parsed.id),得到的是舍入后的数字,而不是响应中的原始字符。
如果 API 无法调整,可以在解析前使用能够保留大整数原始文本的专用解析库;但这会增加前端复杂度。能够控制接口时,优先让服务端把标识符和高精度值输出为字符串。
{
"orderId": 9223372036854775807,
"amount": 9999999999999999
}{
"orderId": "9223372036854775807",
"amount": "9999999999999999",
"amountScale": 2
}不同类型数据的解决方案
标识符通常不参与数学运算,因此应按字符串处理。需要整数运算时,可在确认原始文本未丢失的前提下转换为 BigInt。金额和小数更适合使用最小货币单位整数,或由 Decimal 类型和字符串共同表示。
| 数据类型 | 推荐传输方式 | 前端处理 |
|---|---|---|
| 订单号、用户 ID、雪花 ID | 字符串 | 保持字符串,不做 Number 转换 |
| 超大整数计数 | 字符串 | 需要计算时转换为 BigInt |
| 金额 | 最小单位整数或十进制字符串 | 使用 Decimal 库或整数运算 |
| 普通页码、数量 | JSON number | 确认值在安全整数范围内 |
API 设计与排查清单
前后端应在接口文档中明确字段是数值还是标识符,不要仅根据数据库列类型决定 JSON 类型。数据库 bigint 用于保存 ID,并不意味着前端也应该把它当作可计算的 number。
排查精度问题时,应对比浏览器 Network 面板中的原始响应文本、JSON.parse 后的值以及界面最终显示值。如果原始响应已经错误,应检查服务端序列化;如果原始响应正确但解析后变化,应调整字段类型或解析方式。
- 确认原始响应中的数字是否已经发生变化。
- 检查字段是否超过 Number.MAX_SAFE_INTEGER。
- 标识符统一使用字符串,并在接口文档中固定类型。
- 避免把字符串 ID 经过 Number、parseInt 或一元加号转换。
- 为最大值、边界值和跨语言序列化增加自动化测试。
JSON 格式化工具能帮助什么,不能替代什么
格式化工具适合查看原始层级、搜索字段、比较文本和发现可疑的大整数。树形预览可以提高排查效率,但如果底层解析器已经把数字转换为 Number,预览结果也可能受到精度限制。
因此,处理金融级金额、数据库 bigint 或业务 ID 时,应以接口原始文本和服务端约定为准。格式化工具是诊断入口,不能替代正确的数据类型设计。
边看边操作
使用在线 JSON 工具检查大整数
格式化接口响应并搜索可疑字段,同时保留原始响应用于精度对比。
FAQ
常见问题
JSON 本身支持 BigInt 吗?
JSON 标准没有 BigInt 类型,只有 number。不同语言可以用自己的大整数类型解析 number,但跨语言传输时通常建议使用字符串。
订单号为什么不应该使用 Number?
订单号是标识符,不需要数学运算,而且可能超过 JavaScript 安全整数范围。使用字符串既能保留前导零,也能避免精度丢失。
金额使用字符串会不会不方便计算?
可以使用十进制字符串配合 Decimal 库,或传输最小货币单位的整数。直接使用浮点数可能产生二进制小数误差。