Go 把 JSON 解码到 interface 后,整数 id 变成 float64
encoding/json 默认把接口中的数字解码为 float64。结构已知时指定 int64 字段,动态结构则使用 UseNumber,并检查数字读取和转换错误。
JSON 中的 id 没有小数点,解码到 map[string]interface{} 后,读取它却得到 float64。调试输出有时还会显示科学计数法,让人误以为编码器把整数改成了另一种值。
本文针对 Go 标准库 encoding/json 的默认接口解码规则,不适用于所有第三方 JSON 库。UseNumber() 从 Go 1.1 起提供;下面沿用 interface{} 写法。
用类型输出确认,不靠数字外观判断
package main
import (
"encoding/json"
"fmt"
"log"
)
func main() {
var data map[string]interface{}
if err := json.Unmarshal([]byte(`{"id":12423434}`), &data); err != nil {
log.Fatal(err)
}
fmt.Printf("%T\n", data["id"])
}
预期输出是 float64。当目标是没有具体数字类型的接口值时,默认解码会用 float64 承接 JSON 数字,不会根据是否有小数点自动选择 int64。
科学计数法只是数字的表示形式,不能单独证明精度已经丢失;序列化后的具体格式也不应被当作判断 Go 动态类型的依据。确认动态类型后,再选择读取方式。
结构已知时,直接使用明确的字段类型
如果 id 的契约就是 int64 范围内的整数,可以直接解码到结构体:
package main
import (
"encoding/json"
"fmt"
"log"
)
type Record struct {
ID int64 `json:"id"`
}
func main() {
var record Record
if err := json.Unmarshal([]byte(`{"id":12423434}`), &record); err != nil {
log.Fatal(err)
}
fmt.Printf("%T %d\n", record.ID, record.ID)
}
预期输出为 int64 12423434。检查解码错误是必要的:输入类型不合适或数值超出目标范围时,不能继续把结果当作完整有效记录。
这一做法以字段结构已知为前提。不要为了一个 id 强行丢弃其他未知字段;结构动态变化时可以保留映射,改用下一种解码方式。
动态结构使用 Decoder.UseNumber
package main
import (
"encoding/json"
"fmt"
"log"
"strings"
)
func main() {
decoder := json.NewDecoder(strings.NewReader(`{"id":12423434}`))
decoder.UseNumber()
var data map[string]interface{}
if err := decoder.Decode(&data); err != nil {
log.Fatal(err)
}
number, ok := data["id"].(json.Number)
if !ok {
log.Fatal("id must be a JSON number")
}
id, err := number.Int64()
if err != nil {
log.Fatal(err)
}
fmt.Printf("%T %d\n", id, id)
}
UseNumber() 必须在 Decode 之前设置。这时接口中的数字保存为 json.Number,用 Int64() 明确请求整数,并处理转换错误。缺失字段、null 或字符串形式的 id 都不会通过示例的类型检查。
json.Number 保存数字文本,但重新 Marshal 时仍编码成 JSON 数字,不等同于普通字符串字段。需要浮点运算时可选择 Float64();这也意味着重新接受浮点表示的边界,不能认为 UseNumber 自动让所有运算变成任意精度计算。
不要对已经默认解码出来的 float64 做 .(int64) 类型断言:断言不是数值转换。若需要改变解码策略,应从原始 JSON 重新解码,避免在中间结果上猜测原始数字格式。
验证时检查整数、非整数、超出 int64 范围的数字和缺失 id,确认错误分支符合契约。切换到 UseNumber 后,还要更新依赖原 float64 类型的读取代码;回退时应一并恢复解码和读取逻辑,不能只改其中一端。


