为什么在 Go 中直接使用 json.Unmarshal 解析数字会变成 float64
JSON标准无整数类型,Go的json.Unmarshal严格遵循规范,所有数字解析为float64,导致大整数精度丢失。需用json.Number或结构体字段加",string"标签主动控制解析路径。
JSON 标准里压根没有整数类型,只有一种叫“Number”的抽象数字。Go 的json.Unmarshal严格遵循这条规范,把所有数字都解析成float64——这是铁板钉钉的标准行为。但如果你要处理大整数(比如 ID 或时间戳),就必须主动绕开这条默认路径,要么用json.Number,要么让结构体字段加上",string"标签,否则精度流失连声招呼都不打。

先说一个核心结论:你往 map[string]interface{} 里扔一个 {"id": 42},取出来的时候,id 的类型一定是 float64,值是 42.0。这不是 Go 的锅,是它严格遵循了 JSON 规范。下面展开讲讲到底怎么回事,以及怎么绕开这个坑。
json.Unmarshal 把所有 JSON 数字都当 float64 是标准行为
这可不是什么 bug,而是 Go 老老实实遵循 RFC 8259 的结果。JSON 规范里并没有整数和浮点数的区别,只有一种叫“Number”的数字类型——123、123.0、1.23e+2 都算 Number。Go 的 encoding/json 包为了设计简单、跨语言一致,把所有 JSON Number 都默认解析成 float64。你传给 map[string]interface{} 一个 {"id": 42},拿出来一查,类型是 float64,值是 42.0。就是这么直接,没商量。
interface{} 接收时数字必为 float64,断言 int64 会 panic
好,现在你写 var data map[string]interface{} 接 JSON,然后想当然地 data["id"].(int64)——运行时必然 panic,因为底层根本不是 int64,是 float64。常见的踩坑写法包括:
int(data["id"].(float64))— 看起来能跑,但数字超过 253 就悄悄丢精度int64(data["id"].(float64))— 同样不安全,而且遇到123.5这种非整数时逻辑完全不对- 用
reflect.TypeOf判断是否为int64— 永远返回float64,毫无意义
这些写法都基于一个错误假设:Go 会自动帮你识别整数。而事实是,Go 不会帮你猜,你只能自己处理。
想保留整数语义?得靠数值特征判断,不是类型反射
如果非要从 []interface{} 或 map[string]interface{} 里识别“逻辑上的整数”,唯一可靠的办法是:先断言成 float64,再检查它是否数学上等于某个整数:
if num, ok := val.(float64); ok {
if num == float64(int64(num)) {
// 是整数,可安全转:int64(num)
} else {
// 是浮点数,比如 3.14
}
}
但注意:int64(num) 对超大数(比如 9223372036854775807)仍然可能截断,因为 float64 在解析阶段就已经失真了。真要稳妥,还得靠下面的方法。
大整数(如 ID、时间戳)必须绕过 float64 中转
一旦数字超过 2^53 - 1(也就是 9007199254740991),float64 就无法精确表示低比特位了。典型场景:前端传 "id": 12345678901234567890,后端收到之后变成了 12345678901234567168。误差出现在 Unmarshal 阶段,后面再怎么转都晚了。
正确做法只有两个方向:
- 前端把数字当字符串发,后端结构体字段声明为
string,或者加json:",string"标签 - 后端用
Decoder.UseNumber(),字段类型写成json.Number,然后调.Int64()(注意如果数字带小数点会 panic)
千万别指望 json.Unmarshal 自动猜你是想要 int 还是 float——它只按规范办事。你得主动控制解析路径,否则精度丢失防不胜防。


































