go框架json解析大整数精度丢失的根本原因是标准库默认将数字转float64,必须主动用json.decoder.usenumber()或json.number/string字段配合前端字符串传输才能避免。

Go 框架(如 Gin、Echo、Fiber)里 JSON 解析默认走 json.Unmarshal,一碰到超 2^53 - 1 的整数(比如雪花 ID、微秒时间戳),立刻丢精度——不是框架问题,是标准库默认行为。必须主动干预,否则前端传 "12345678901234567890" 都救不回来。
用 json.Decoder.UseNumber() 替换默认 Unmarshal
框架底层通常封装了 json.Unmarshal,你没法直接改调用点。得在绑定前接管解码器。
- Gin 中推荐在中间件里替换
ctx.Request.Body,用json.NewDecoder+.UseNumber()重解析;别依赖ctx.BindJSON(),它绕不开 float64 - Echo 可通过自定义
echo.HTTPErrorHandler或提前用echo.DefaultHTTPErrorHandler拦截,再用json.NewDecoder(r.Body).UseNumber()手动解 - Fiber 的
c.BodyParser()不支持 UseNumber,必须用c.RequestBody()拿原始字节,再喂给json.NewDecoder(bytes.NewReader(b)).UseNumber() - 所有场景下,
map[string]interface{}取值后必须先断言为json.Number,再调.Int64()或.String();直接int64(v.(float64))会 panic 或错值
struct 字段声明为 json.Number 并加 json:",string"
比全局 UseNumber 更精准,适合已知关键字段(如 ID、TraceID)的场景,但要求前后端约定好传输格式。
- 字段类型写
ID json.Number `json:"id"`,解码后调u.ID.Int64()—— 它内部检查溢出,比strconv.ParseInt(u.ID.String(), 10, 64)少一层 error 处理 - 若字段可能超
int64(如 128 位 ID),直接用string类型 +json:",string"标签:ID string `json:"id,string"`,前提是前端发的是{"id": "12345678901234567890"},不是裸数字 - 别混用:
json.Number字段不能配",string";string字段配",string"才生效;两者都强制跳过 float64 转换,但语义不同 - 嵌套结构体里同样适用,但
json.Number不自动穿透 map 或 slice,得逐层声明
避免 json.RawMessage 和 map[string]interface{} 的常见误用
json.RawMessage 看似“延迟解析”,实际只是把字节存下来,后面一旦调 json.Unmarshal 还是进 float64;而 map[string]interface{} 默认全转 float64,不加 UseNumber() 就等于放弃精度。
- 别写
Extra json.RawMessage `json:"extra"`然后等后续再解——只要最终解析路径经过json.Unmarshal,就可能掉坑 - 用
map[string]interface{}接动态字段时,必须配合UseNumber(),且取值后手动断言:v, ok := data["id"].(json.Number); if ok { idStr := v.String() } - 第三方 SDK 返回
map[string]interface{}(如某些 MQ 客户端、DB 驱动),无法改其解码逻辑,只能在你拿到数据后,用反射或遍历把所有float64值尝试还原——不可靠,不如推动上游支持UseNumber - 日志或调试时打印
map[string]interface{},看到1.234567e+18就该警觉:这已经是 float64 表示,原始文本早没了
最易被忽略的一点:精度丢失发生在 JSON 解析**第一刻**,不是你在代码里做运算时。哪怕你全程用 int64 变量、开 -gcflags="-l" 关闭内联,只要输入字节流里是裸数字、解码器没开 UseNumber 或字段没声明为 json.Number / string,就注定失真。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











