Gin 的 BindJSON 将大整数转为 float64 是因底层调用 json.Unmarshal 默认将所有 JSON 数字解析为 float64,超过 2⁵³−1(9007199254740991)即精度丢失;正确方案是前端发字符串并配合后端使用 json.Number 或 string 字段加 ",string" 标签。

为什么 Gin 的 BindJSON 会把大整数变成 float64?
因为 Gin 默认调用 json.Unmarshal,而 Go 标准库的 JSON 解析器对数字类型不做区分——一律先转成 float64。一旦原始数字超过 2^53 - 1(即 9007199254740991),float64 就无法精确表示,比如雪花 ID 12345678901234567890 解出来可能是 12345678901234567168,差了几百。
Gin 中绕过 BindJSON 改用 UseNumber() 的实操路径
不能直接改 c.BindJSON() 的行为,得在中间件里接管请求体解码流程:
- 用
c.Request.Body拿到原始字节流,但注意 Body 只能读一次,需用io.NopCloser包装回写 - 创建
json.NewDecoder(c.Request.Body),立刻调用.UseNumber() - 再调
.Decode(&target),此时 JSON 数字会被存为json.Number类型(本质是 string) - 后续取值必须显式转换:
idStr := idNum.String()或idInt, _ := idNum.Int64(),不能直接断言int64
struct 字段声明为 json.Number 还是 string?
取决于你是否需要后端做数值运算:
使用约定式提交(Conventional Commits)生成标准化的 Git 提交信息。适用于以下场景: (1) 暂存更改时需要提交信息; (2) 审查 diff 时希望获得结构化摘要; (3) 编写 PR 描述或变更日志(changelog)条目; (4) 用户明确要求“为本次更改编写提交信息”。 该功能会分析已暂存/未暂存的 diff,并生成按类型(type)划分范围的提交信息。
- 字段确定是 ID/TraceID 类型、且前端发的是
{"id": "12345678901234567890"}(带引号的字符串),就用ID string `json:"id,string"`—— 最省事,不依赖UseNumber - 字段可能混传(前端有时发数字、有时发字符串),或你需要保留解析后的类型控制权,就用
ID json.Number `json:"id"`,再手动调.Int64()或.String() -
json.Number字段加,string标签会 panic;string字段加,string才生效 —— 两者语义不同,别混用
嵌套结构里怎么保证每层都不丢精度?
json.Number 不自动穿透 map 或 slice,必须逐层显式声明:
- 如果
User里有Profile,而Profile.ID是大整数,那Profile结构体里的ID字段也得是json.Number或string - 用
map[string]interface{}接收时,所有数字字段都得先断言为json.Number,再转;直接v.(float64)会 panic 或错值 - 别指望
UseNumber()全局生效后就能在任意嵌套层级安全取int64—— 每一层都得自己做类型检查和转换
实际最难的不是加一行 UseNumber(),而是前后端约定好传输格式并统一处理路径:前端发字符串,后端用 string 字段;或者前端坚持发数字,后端就得在每一层结构体里都用 json.Number 并手动转。漏掉一层,精度就丢了。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










