Go 的 json.Marshal 默认用 %g 格式化 float64,导致大整数变科学计数法或末位错乱;根本原因是 float64 无法精确表示所有 64 位整数,而 JSON 要求十进制字面量精确。

Go 默认的 json.Marshal 会把 float64 转成科学计数法或截断小数位,尤其在处理金额、时间戳、长整型 ID(如 Snowflake)时,精度直接丢失 —— 这不是 RabbitMQ 的问题,是序列化环节就错了。
为什么 json.Marshal 默认行为会丢精度?
Go 的 json 包对 float64 类型默认使用 %g 格式化,它会在小数位过多或数值过大时自动切为科学计数法(如 1.23456789e+12),而很多下游服务(比如 JS 前端、Java 服务、数据库导入工具)无法安全解析这种格式;更隐蔽的是,当 float64 存的是精确整数(如 9223372036854775807),%g 可能因浮点表示误差输出错位值(如末尾变成 ...806)。
常见错误现象:
• 发送 {"id": 9223372036854775807},消费者收到 "id": 9223372036854775806
• 金额字段 100.00 被转成 100 或 1e2,下游做等值判断失败
- 根本原因:Go
float64无法精确表示所有 64 位整数,而 JSON 规范要求数字字面量必须是精确的十进制表示 - 关键点:RabbitMQ 本身不解析 JSON,它只透传字节流 —— 所以“精度”完全由你发出去的字符串决定
用 json.Encoder + SetEscapeHTML(false) 不够,得禁用 float 转换
仅调用 json.Marshal 或 json.NewEncoder 默认行为仍走 %g。必须显式控制浮点输出格式:
- 不要依赖
json.Marshal直接序列化含 float64 的结构体 - 对所有可能承载高精度数字的字段,改用
string类型存储原始字符串(如"9223372036854775807"),或用json.Number中间代理 - 若必须用 float64 字段,发送前统一转成
json.Number:例如json.Number(strconv.FormatFloat(v, 'f', -1, 64)),其中'f'强制十进制、-1表示不限小数位
简短示例:
type Order struct {
ID json.Number `json:"id"`
Amount json.Number `json:"amount"`
}
order := Order{
ID: json.Number(strconv.FormatInt(9223372036854775807, 10)),
Amount: json.Number("100.00"),
}
data, _ := json.Marshal(order) // 输出 {"id":"9223372036854775807","amount":"100.00"}
避免用 map[string]interface{} 直接塞 float64
这是最常踩的坑:动态构造消息时习惯写 m["id"] = float64(9223372036854775807),然后 json.Marshal(m) —— 此时 json 包完全不知道你的意图,直接按 float64 默认规则输出,精度必然丢失。
- 正确做法:提前把数字转成
string再塞进 map:m["id"] = "9223372036854775807" - 或者统一用
json.RawMessage封装已校验过的 JSON 字符串片段 - 如果用第三方库(如
go-json),确认其是否支持UseNumber()或类似选项;标准库需手动干预
消费者侧也要保持一致:别用 json.Unmarshal 直接进 float64
即使你发出了精确字符串,如果消费者用 map[string]interface{} 解析,JSON 包默认仍会把数字字段转成 float64,又绕回精度丢失。必须主动约束:
- 定义明确结构体,ID/金额等字段用
string或json.Number - 解析后立刻用
json.Number.Int64()或.Float64()(注意:后者仍有风险) - 关键业务字段(如订单 ID、资金变动)建议全程用
string传输,彻底规避浮点陷阱
真正可靠的做法不是“怎么让 float64 不丢”,而是“根本别让它参与 JSON 序列化”。RabbitMQ 消息体是字节流,你发什么,它就传什么 —— 精度控制权始终在你手上,不在 broker。


















