在 Go 项目中,我们经常使用 map[string]interface{} 来表示动态字段,或者用 JSON 序列化对象存入数据库、发送到 MQ。然而,当存储或传输 大整数(int64) 时,往往会出现精度丢失的问题。本文通过一个示例来详细分析原因,并给出解决方案。
1️⃣ 场景示例
假设我们有一个动态 Extra 字段,用于存储一些扩展信息,其中 oldTask 是一个 int64 大整数:
go
package main
import (
"encoding/json"
"fmt"
)
func main() {
// 模拟 Extra 字段
extra := make(map[string]interface{})
extra["oldTask"] = int64(7587721483007868979)
fmt.Println("【1】原始写入 int64:")
fmt.Printf("type=%T, value=%v\n\n", extra["oldTask"], extra["oldTask"])
}
输出:
go
【1】原始写入 int64:
type=int64, value=7587721483007868979
✅ 在 Go 内存中,extra["oldTask"] 正确保存了 int64 值。
2️⃣ JSON 序列化
接下来我们将这个 map 序列化为 JSON 字符串(就像你在 BuildRiskPointsStrategy 中做的):
css
bytes, _ := json.Marshal(extra)
fmt.Println("【2】json.Marshal 后:")
fmt.Println(string(bytes), "\n")
输出:
json
【2】json.Marshal 后:
{"oldTask":7587721483007868979}
✅ Go 的 json.Marshal 对 int64 的处理是安全的,大整数不会丢失精度。
3️⃣ JSON 反序列化到 map[string]interface{}
然而问题出现了,当我们反序列化 JSON 到 map[string]interface{} 时:
go
var extra2 map[string]interface{}
_ = json.Unmarshal(bytes, &extra2)
fmt.Println("【3】json.Unmarshal 后:")
fmt.Printf("type=%T, value=%v\n\n", extra2["oldTask"], extra2["oldTask"])
输出:
go
【3】json.Unmarshal 后:
type=float64, value=7587721483007869000
❌ 注意:
- 反序列化后,
extra2["oldTask"]变成了 float64 - 原始的 int64 值
7587721483007868979精度丢失 - 这是 Go 标准库
encoding/json的默认行为:map[string]interface{}中的数字全部解析成float64。
4️⃣ 再转回 int64
如果我们强行把它转回 int64:
css
lost := int64(extra2["oldTask"].(float64))
fmt.Println("【4】float64 → int64 后:")
fmt.Println("value =", lost)
输出:
go
【4】float64 → int64 后:
value = 7587721483007869000
- 可以看到,数字已经不再精确,丢失了最后几位。
5️⃣ 对比差值
go
fmt.Println("【5】对比:")
fmt.Println("是否相等:", lost == int64(7587721483007868979))
fmt.Println("差值:", lost-int64(7587721483007868979))
输出:
arduino
【5】对比:
是否相等: false
差值: 79
- 原始值与反序列化后值不一致
- 这就是"精度丢失"的根本原因
6️⃣ 问题分析
原因总结:
-
Go 的
encoding/json对map[string]interface{}反序列化时:- 所有数字默认是
float64 - float64 最大安全整数范围是
-2^53 ~ 2^53
- 所有数字默认是
-
当 int64 值超出 2^53 时,float64 表示不精确 → 精度丢失
Go 官方文档说明:JSON numbers without decimal points are decoded as float64 when the target is
interface{}。
7️⃣ 解决方案
方案 1:使用字符串存储大整数
less
extra["oldTask"] = fmt.Sprintf("%d", oldTaskInt64)
- JSON 序列化后:
"oldTask":"7587721483007868979" - 写入数据库 / MQ / ES 时不会丢精度
- 读取时再用
strconv.ParseInt转回 int64
方案 2:使用 json.Number 解析
css
var extra2 map[string]interface{}
decoder := json.NewDecoder(bytes.NewReader(jsonBytes))
decoder.UseNumber() // 使用 json.Number 代替 float64
decoder.Decode(&extra2)
extra2["oldTask"]类型是json.Number- 可以安全转成
int64或string
css
num, _ := extra2["oldTask"].(json.Number).Int64()
方案 3:使用 struct 明确类型
go
type RiskExtra struct {
OldTask int64 `json:"oldTask"`
}
- 避免 map[string]interface{} → float64
- JSON 反序列化会直接解析为 int64
✅ 结论
- Go map[string]interface{} + JSON 是大整数丢失精度的高发场景
- 精度丢失的原因:JSON 默认解析数字为 float64
- 解决方案:
- 使用字符串存储大整数
- 使用
json.Number - 或者尽量使用 struct 明确类型