登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  Golang >  Go教程

Go 解析 JSON 大整数怎么避免精度丢失

来源:17golang原创

时间:2026-09-05 23:02:45 391浏览 收藏

接口里的订单号、雪花 ID 或计数器一旦变大,Go 代码却把 JSON 解码成 map[string]any,就可能出现末尾数字变化。原因不是 JSON 不能表示大整数,而是 encoding/json 在没有目标类型时默认把 JSON 数字放进 float64。解决方法很明确:固定结构优先用 int64/uint64 字段,动态结构在 json.Decoder 上调用 UseNumber();如果这个值只是标识符且不参与运算,协议层直接传字符串更稳妥。

要点速览
  • 不要把需要精确保存的整数先解码到 interface{} 再猜类型。
  • 结构体字段能确定范围时,用整数类型让解码器直接做范围检查。
  • 字段动态时用 UseNumber() 保留字面量,调用 Int64() 前检查错误。

为什么 map[string]any 会改写大整数

下面的写法看起来通用,却隐藏了类型转换:

var data map[string]any
err := json.Unmarshal([]byte(`{"id":9007199254740993}`), &data)
if err != nil {
    log.Fatal(err)
}
fmt.Printf("%T %v\n", data["id"], data["id"])

在默认规则下,data["id"] 的动态类型是 float64。浮点数的有效整数精度有限,转换发生后,原 JSON 文本中的每一位就不再都能恢复。这里再调用 fmt.Sprintf 或强转整数,只是在已经丢失信息的结果上继续操作。

JSON 数字从原始文本进入 float64 后出现精度边界的静态关系图
图1:原始 JSON 数字进入无目标类型解码时,会在 interface 与 float64 边界处失去精确字面量。

固定字段直接解码为 int64 或 uint64

如果响应格式稳定,最简单的方案是声明数据模型。订单号可能是正整数,就使用 uint64;业务允许负数时使用 int64

type Response struct {
    ID    uint64 `json:"id"`
    Total int64  `json:"total"`
}

var resp Response
err := json.Unmarshal([]byte(`{"id":9007199254740993,"total":12}`), &resp)
if err != nil {
    return fmt.Errorf("decode response: %w", err)
}
fmt.Println(resp.ID)

这种方式的优点是边界清楚:数字超出目标类型范围时,解码会返回错误,而不是静默保留一个看似正常的浮点数。注意 JSON 字段若偶尔返回字符串,整数类型不会自动替你兼容两种格式,需要先统一接口协议,或为该字段实现自定义 UnmarshalJSON

动态字段用 Decoder.UseNumber 保留原始数字

当字段来自扩展属性、规则引擎或不固定的第三方 JSON,不能为每个键都定义结构体时,使用 UseNumber()

dec := json.NewDecoder(strings.NewReader(`{"id":9007199254740993,"ratio":1.25}`))
dec.UseNumber()

var data map[string]any
if err := dec.Decode(&data); err != nil {
    return err
}

number, ok := data["id"].(json.Number)
if !ok {
    return fmt.Errorf("id is not a JSON number")
}
id, err := number.Int64()
if err != nil {
    return fmt.Errorf("id is not int64: %w", err)
}
fmt.Println(id)

UseNumber() 只改变动态数字的落地类型,不会替你决定它最终应当是整数、浮点数还是字符串。json.Number.String() 返回数字字面量,适合继续交给 big.Int 或其他高精度库;Int64() 只适合确实落在 int64 范围内的值,必须检查返回错误。

UseNumber 保留 JSON 数字字面量并分流到 String 和 Int64 的静态结构图
图2:动态 JSON 先保留为 json.Number,再按是否需要运算分流到字面量或 int64 转换。

三种方案怎么选才不会留下隐患

场景推荐类型判断重点
字段固定且参与整数运算int64/uint64让解码阶段完成范围检查
字段动态但仍需判断数值类型json.Number先保留字面量,再显式转换
值是 ID、编码,不参与数学运算JSON string协议允许时从源头避免数字语义

还要区分“解析不丢精度”和“后续运算不溢出”。json.Number 只能保存输入文本;如果要计算超过 int64 的整数,应把 String() 交给 math/big,不要先调用 Float64()。如果服务边界由你控制,雪花 ID 这类只做比较、拼接和展示的值通常用字符串最省心。

常见问题

UseNumber 能让所有数字自动变成 int64 吗?

不能。它让动态数字变成 json.Number,整数或小数的具体处理仍由调用方决定。

已经解码成 float64 还能恢复原值吗?

通常不能。若原始字节仍在,应重新从原文解码,并在解码前选择结构体、UseNumber 或字符串方案。

为什么结构体里的 int64 不需要 UseNumber?

因为字段目标类型已经明确,解码器会直接按整数字段解析;UseNumber 主要解决目标是 interface{} 的动态结构。

最终可以记住一句话:需要精确整数就不要让它先经过默认的 float64。先确定数据模型,再决定是让结构体接收整数、让 json.Number 保留文本,还是把标识符设计成字符串。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>