JSON序列化慢因标准库依赖反射,easyjson编译期生成代码可提速3–10倍;推荐用json.RawMessage或gjson延迟/按需解析;避免interface{}解包,应定义精简结构体。

为什么 json.Marshal 和 json.Unmarshal 会慢?
Go 标准库的 encoding/json 包在运行时依赖反射(reflect)遍历结构体字段、查找标签、动态类型判断,这带来明显开销。尤其当结构体嵌套深、字段多、或高频调用(如 API 服务每秒数千请求)时,CPU 和 GC 压力会快速上升。
常见现象包括:pprof 显示 reflect.Value.Interface 或 json.(*decodeState).object 占高;GC pause 时间随 JSON 流量增长而波动;相同数据量下比 Protobuf 或 msgpack 序列化慢 2–5 倍。
这不是 bug,而是设计取舍:标准库优先保证通用性与开发效率,而非极致性能。
用 easyjson 替代反射式序列化
easyjson 在编译期为结构体生成专用的 MarshalJSON / UnmarshalJSON 方法,完全绕过 reflect,实测吞吐提升 3–10 倍,内存分配减少 80%+。
立即学习“go语言免费学习笔记(深入)”;
- 安装:
go install github.com/mailru/easyjson/...@latest - 为结构体生成代码:
easyjson -all user.go(会生成user_easyjson.go) - 结构体需带
jsontag,且导出字段首字母大写;不支持匿名字段嵌套自动展开(需显式命名) - 生成代码默认使用
unsafe加速字符串转换,若需禁用(如 FIPS 合规场景),加参数-no-unsafe
type User struct {
ID int `json:"id"`
Name string `json:"name"`
Tags []string `json:"tags,omitempty"`
}
// 生成后,可直接调用:
b, _ := user.MarshalJSON() // 非 json.Marshal(user)
user.UnmarshalJSON(b) // 非 json.Unmarshal(b, &user)
避免反复解析同一 JSON 字段(如 Webhook payload)
很多服务接收固定格式的第三方 JSON(如 Stripe、Slack webhook),但每次请求都走完整 json.Unmarshal + 字段提取,浪费大量 CPU。
更高效的做法是:用 json.RawMessage 延迟解析关键子字段,或用 gjson 直接按路径提取值。
-
json.RawMessage适合“主结构体稳定、子内容动态”的场景(如 event.type 决定后续解析逻辑) -
gjson.GetBytes(data, "data.items.#.price")比完整反序列化快 5–20 倍,且零内存分配(返回字符串视图) - 注意:
gjson不校验 JSON 合法性,输入非法时返回空;若需强校验,仍应先用json.Valid快速预检
var payload struct {
Event string `json:"event"`
Data json.RawMessage `json:"data"`
}
json.Unmarshal(body, &payload)
if payload.Event == "order.created" {
price := gjson.GetBytes(payload.Data, "total").Number()
}
小心 interface{} 和 map[string]interface{} 的陷阱
用 json.Unmarshal 解到 interface{} 或 map[string]interface{} 看似灵活,实则代价极高:所有数字转为 float64,字符串重复分配,嵌套 map 深度越深 GC 越频繁。
生产环境应严格避免将它作为通用解包目标。替代方案:
- 定义最小必要结构体(哪怕只取 2–3 个字段),用
easyjson或标准库解析 - 若必须动态,改用
jsoniter并启用UseNumber()避免 float64 强制转换 - 对超大 payload(>1MB),考虑流式解析:
json.NewDecoder(r).Decode(&v),防止一次性加载全部内存
真正难处理的是混合类型字段(如 "value": 42 或 "value": "hello"),这时应优先在协议层约束类型,而不是在 Go 层做运行时类型推断。











