go 的 json.unmarshal 默认将大数字解析为 float64,导致超过 2⁵³−1 的整数精度丢失;应优先使用 json.number 或 string 类型配合前端字符串传输,或自定义 unmarshaljson 实现无损解析。

Go 的 json.Unmarshal 默认把大数字当 float64 解析
JavaScript 的 Number 只能安全表示 ±2⁵³−1 范围内的整数,超过这个值(比如 17 位以上 ID、时间戳微秒级)传给前端就可能四舍五入或变零。Go 默认用 float64 接 JSON 数字,哪怕你声明的是 int64 字段——只要数字超出 float64 精度范围,反序列化时就已失真。
实操建议:
- 所有可能超
2^53 - 1(即9007199254740991)的整数字段,别用int64或float64直接接收,改用string或自定义类型 - 在结构体字段上加
json:",string"标签,强制 Go 把 JSON 数字当字符串解析(前提是前端也发字符串,比如"12345678901234567890") - 如果无法控制前端发字符串,就得用
json.RawMessage+ 手动解析,避免经过float64中转
用 json.Number 替代 float64 做中间容器
json.Number 是 Go 标准库提供的无损数字容器,本质是字符串,只在需要时才转成具体数值类型。它不自动解析、不损失精度,是处理大整数最轻量的方案。
实操建议:
- 结构体字段声明为
json.Number类型,比如ID json.Number `json:"id"` - 需要转整数时,调用
.Int64()(注意:它会 panic 如果值不是合法整数,比如带小数点或溢出) - 转字符串更安全:
id.String(),直接拿到原始 JSON 文本,前端可原样消费 - 别对
json.Number做算术运算,它不是数值类型;需先转再算,且务必检查错误
前端配合:必须发字符串,不能依赖 Number 自动转换
即使 Go 层用了 json.Number 或 string,如果前端 JavaScript 还是用 JSON.parse('{ "id": 12345678901234567890 }'),那在 JS 解析那一刻就丢了精度——Go 根本没机会干预。
实操建议:
- 后端 API 文档明确要求:所有大整数字段(如
id、trace_id、created_at_us)必须以字符串形式传输,例如"id": "12345678901234567890" - 前端发请求前,用
JSON.stringify()配合 replacer 把大数字转字符串,或用BigInt+.toString()(注意兼容性) - 如果用 Axios,可在
transformRequest里统一处理;React Query 或 SWR 的queryFn里提前格式化
自定义 UnmarshalJSON 是终极可控方案
当字段语义固定(比如就是 ID)、且必须强类型为 int64 时,自己实现 UnmarshalJSON 方法,绕过默认 float64 解析路径,是最稳妥的做法。
实操建议:
- 定义新类型:
type ID int64,然后为它实现UnmarshalJSON([]byte) error - 方法内用
json.Unmarshal先解析到json.Number,再调用.Int64()并捕获json.InvalidUnmarshalError和溢出错误 - 别用
strconv.ParseInt(string(b), 10, 64)直接解析原始字节——要跳过空白、处理负号、校验是否全数字,标准库的json.Number.Int64()已做好这些 - 注意:这种类型不能直接用在
database/sql的 Scan 中,需额外实现Scanner接口
真正麻烦的从来不是“怎么写”,而是前后端约定是否落地到每一行 JSON 字符串。只要有一处漏掉引号,精度就没了,而且很难排查——因为日志里看数字都“长得一样”。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











