go 的 json.unmarshal 默认将 json 数字全解析为 float64,导致大整数(>2⁵³−1)精度丢失;解决方法包括:全局使用 json.decoder.usenumber()、结构体字段声明为 json.number 并调用 .int64() 安全转换,或针对关键字段显式处理。

json.Unmarshal 默认把数字全转成 float64
Go 的 encoding/json 包默认把 JSON 中所有数字(无论整数还是小数)都解析为 float64。这意味着超过 2^53 - 1(约 9e15)的整数会丢失精度——比如 9223372036854775807(int64 最大值)解析后可能变成 9223372036854776000。
这不是 bug,是 float64 本身的表示限制。但如果你在处理 ID、时间戳、金额等必须精确的整数字段,这就很致命。
实操建议:
- 全局禁用默认数字解析:在
json.Decoder上调用UseNumber()方法,让所有数字以字符串形式暂存 - 只对需要高精度的字段启用
json.Number,避免全量转 string 带来的额外解析开销 - 别依赖
json.RawMessage来绕过——它只是延迟解析,后续仍可能进 float64
怎么用 json.Number 解析指定字段
json.Number 是一个字符串类型别名,用来“占位”JSON 数字,直到你显式转成 int64 或 string。它不自动转换,也不参与算术——这是设计,不是缺陷。
常见错误现象:直接把 json.Number 赋给 int64 变量,编译报错;或用 strconv.ParseInt(n.String(), 10, 64) 却没检查 error,导致 panic。
实操建议:
- 结构体字段声明为
json.Number类型,例如:Id json.Number `json:"id"` - 后续转
int64时务必用n.Int64()(内部已封装 error 处理),而不是手动strconv - 如果字段可能超 int64 范围(比如 128 位 ID),就保留为
string,用n.String()获取原始文本
type User struct {
Id json.Number `json:"id"`
Name string `json:"name"`
}
var u User
json.Unmarshal(data, &u)
id, err := u.Id.Int64() // 安全,err 非 nil 表示溢出或非法格式
if err != nil {
// 处理溢出,比如记录日志、fallback 到 string
}
json.Number 在嵌套结构和切片里怎么用
它不支持自动递归生效。如果你有 []map[string]interface{} 或深层嵌套的 map[string]interface{},UseNumber() 会让所有数字变成 json.Number 字符串,但你得自己遍历、识别、转换——标准库不帮你做这层。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
使用场景:Webhook 接收第三方 JSON,字段结构不固定,但某些 key(如 "order_id"、"account_balance")必须保精度。
实操建议:
- 对不确定结构的数据,先用
json.RawMessage或map[string]json.RawMessage接收,再按需解析关键字段 - 配合
json.Unmarshal两次:第一次用UseNumber()得到 map,第二次对目标字段用json.Unmarshal+json.Number类型 - 别试图给
interface{}断言json.Number——它实际是string,断言失败率高;优先用map[string]json.Number显式定义
性能和兼容性要注意什么
json.Number 本质是 string,每次调用 .String() 或 .Int64() 都涉及内存拷贝或解析,高频场景下比原生 int64 字段慢 2–3 倍。但比起精度丢失,这点开销通常可接受。
兼容性方面:json.Number 只影响解码(Unmarshal),编码(Marshal)时它会自动转回数字——只要内容是合法数字字符串,不会多出引号。
容易踩的坑:
- 用
json.Number字段做 struct 比较(==)会失败,因为底层是 string,且大小写/空格敏感 - 日志打印时直接写
fmt.Printf("%v", n)看到的是带引号的字符串(如"12345"),想干净输出得用n.String() - 第三方库(如
gin.Context.BindJSON)默认不启用UseNumber(),需手动配置 decoder
真正麻烦的不是怎么写那几行代码,而是得想清楚:哪些字段真的不能丢精度?它们在业务里是被比较、存储、还是仅展示?漏判一个关键字段,线上查半天。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










