JSON.parse 大数精度丢失:一个让前后端互相甩锅的 Bug

问题场景

某天上线后,运营反馈:"用户详情页打开后,跳转到其他页面时 ID 不对,每次点进去都是另一个人。"

排查发现,后端返回的用户 ID 是这样的:

json 复制代码
{
  "userId": 19028374651234567891,
  "name": "张三"
}

前端用 JSON.parse 解析后,userId 的值变成了:

复制代码
19028374651234567000

最后几位全变了。 跳转时拿这个错误的 ID 去请求,自然查到了另一个人。

最坑的是------这个 Bug 不是必现的。只有 ID 超过一定位数才会触发,小 ID 完全正常,导致开发环境测不出来。


原因分析

JavaScript 的数字限制

JavaScript 中所有数字都遵循 IEEE 754 双精度浮点数标准 (64-bit)。它能安全表示的整数范围是:

diff 复制代码
- (2⁵³ - 1)  ~  2⁵³ - 1
即 -9007199254740991  ~  9007199254740991

超过这个范围的整数,位数字太多时,JS 会做舍入处理

javascript 复制代码
console.log(19028374651234567891);
// 输出: 19028374651234567000
//        ↑ 末尾被截断,精度丢失

你可以用 Number.isSafeInteger() 来验证:

javascript 复制代码
Number.isSafeInteger(19028374651234567891); // false

JSON.parse 的静默丢失

问题在于 JSON.parse 不会报错------它默默地返回一个精度丢失的数字,没有任何警告:

javascript 复制代码
const data = JSON.parse('{"id": 19028374651234567891}');
console.log(data.id); // 19028374651234567000
// 完全静默,没有异常,没有 console.warn

这就是为什么这个 Bug 特别难排查:没有报错、没有警告,只是数据不对。

谁的锅?

论点 分析
后端 "我数据库存的明明是完整 ID" ✅ 数据源没错
前端 "我收到的 JSON 字符串也是完整的" ✅ 传输层没错
问题 出在 JSON.parse 的解析阶段 💥 这里炸了

谁都不背锅,是 JSON 规范与 JS Number 类型之间天然的鸿沟


解决方案

方案一:后端改为字符串返回(推荐)

后端将大整数统一以字符串形式传给前端:

json 复制代码
{
  "userId": "19028374651234567891",
  "name": "张三"
}

前端无感知,直接当 string 使用。但要注意接口文档明确标注哪些字段是字符串型 ID,避免混用。

方案二:后端改用 Lossless JSON 库(后端侧)

如果后端不方便改类型,可以用支持无损数字的序列化库:

  • Java : 使用 Jackson + @JsonSerialize(using = ToStringSerializer.class)
  • Go : 使用 json:"userId,string" tag
  • Node.js : 使用 lossless-json

方案三:前端自己用 reviver 处理(前端侧)

JSON.parse 接受第二个参数 reviver,可以在解析时对特定字段做处理:

javascript 复制代码
function safeParse(json) {
  return JSON.parse(json, (key, value) => {
    // 对指定的大数字段做字符串保留
    if (key === 'userId' || key === 'id') {
      return String(value);
    }
    return value;
  });
}

const data = safeParse('{"userId": 19028374651234567891}');
console.log(data.userId); // "19028374651234567891" ✅

但这个方法有个致命缺陷:reviver 执行时,数字已经被截断了 。所以如果 JSON 字符串里数字本身就超出了安全范围,reviver 拿到的已经是错误的值,String(value) 也救不了。

javascript 复制代码
// reviver 拿到的是已经被截断的值
JSON.parse('{"id": 19028374651234567891}', (key, val) => {
  console.log(val); // 19028374651234567000 ❌ 已丢失精度
  return String(val); // "19028374651234567000" ❌ 错的
});

所以方案三只能用于后端已经返回字符串的场景,实际用途有限。

方案四:使用 BigInt + 自定义 JSON 解析

对于确实需要前端解析大整数的场景,可以用 json-bigint 库:

javascript 复制代码
import JSONbig from 'json-bigint';

const data = JSONbig.parse('{"id": 19028374651234567891}');
console.log(data.id.toString()); // "19028374651234567891" ✅

原理是解析时将大整数转为 BigInt 类型。

方案五:HTTP 层面用 proto3 的 int64 类型(终极方案)

对于 gRPC / protobuf 项目,proto3 的 int64 在 JS 环境中默认被转为 string,天然避坑。


实际项目中的鉴别方法

如何快速知道你的项目有没有这个隐患?

javascript 复制代码
// 在控制台跑一下
function checkSafeRange() {
  const testVal = 9007199254740993;
  console.log('原始值:', testVal);
  console.log('是否安全:', Number.isSafeInteger(testVal));
  console.log('解析后:', JSON.parse(String(testVal)));
}

// 更实用:扫描所有 ID 字段
fetch('/api/user/1')
  .then(r => r.json())
  .then(data => {
    Object.entries(data).forEach(([key, val]) => {
      if (typeof val === 'number') {
        console.log(key, Number.isSafeInteger(val) ? '✅' : '❌ 精度风险', val);
      }
    });
  });

要点总结

  1. JS 安全整数范围±2⁵³-1,超过就丢精度
  2. JSON.parse 静默丢失------不报错、无警告,极难排查
  3. 推荐方案:后端将大数字 ID 改为字符串格式返回
  4. reviver 不顶用------它拿到的已经是截断后的值
  5. Node.js/Go/Java 后端都有对应的序列化方案避免此问题
  6. 早发现 :在开发阶段就可以用 Number.isSafeInteger 给所有数字 ID 做断言

一句话总结 :JSON 里的超长数字在 JSON.parse 时会悄无声息地丢失精度。最省心的修法是后端把超过 16 位的数字全换成字符串。别让 JSON.parse 吃掉你的 ID。

相关推荐
Helen_cai1 小时前
OpenHarmony 项目统一全局样式、尺寸、色彩主题封装 ThemeUtil(API23)
开发语言·前端·javascript·华为·harmonyos
郑州光合科技余经理1 小时前
家政O2O平台解析:从0搭建上门预约小程序解决方案
android·java·开发语言·前端·小程序·架构·php
小徐_23332 小时前
AI 写 wot-ui 总在猜 API?我们把 Skills、MCP 和 CLI 都配好了
前端·uni-app·ai编程
winfredzhang2 小时前
用 wxPython + ECharts + 阿里矢量地图,做一个可离线兜底的上海雨量看板
前端·javascript·echarts
索西引擎2 小时前
【React】Immer.js 在现代 Redux 生态中的角色:不可变性保障的工程化实现与开发体验优化
前端·javascript·react.js
kisshyshy2 小时前
《川剧变脸 × React 状态管理?我在浏览器里跑了个端侧大模型》
前端·react.js·架构
只一2 小时前
端侧AI实战第二章:React组件工程化 + 事件系统 + 可复用进度条(WebGPU模型加载底座)
前端·react.js
大卫陈2 小时前
微信小程序虚拟支付实战:从「支付能力被限制」到沙箱调通的全过程
前端·后端
武子康2 小时前
vLLM 0.25.1:服务没有报错,为什么仍会生成垃圾 Token(5 级正确性门禁 + 自动回滚条件)
前端·人工智能·后端