JSON 编码性能怎么优化?struct tag 有哪些细节?
一句话结论:encoding/json 靠反射实现,开箱即用但不算快;性能敏感时减少反射面(结构体小、字段少)、复用编码对象与缓冲,必要时换 jsoniter/easyjson 或手写 MarshalJSON。struct tag 控制字段名、忽略、空值省略与数字字符串化,其中 omitempty 的“零值即省略”最容易踩坑。
struct tag 速查
| tag | 效果 | 坑 |
|---|---|---|
| json:"name" | 指定序列化字段名 | 不写则默认用 Go 字段名 |
| json:"-" | 完全忽略该字段 | 与 “,-” 不同(后者字段名叫 -) |
| ,omitempty | 零值省略 | false/0/""/空切片都会被省,无法区分“没填”与“填了零值” |
| ,string | 数字/布尔编码为 JSON 字符串 | 顺序写法 json:"id,string,omitempty" |
| 无 tag 的未导出字段 | 直接忽略 | 反序列化进私有字段既不报错也不生效 |
要区分“没传”与“传了零值”时,用指针字段(nil 表示没传)或自定义 MarshalJSON/UnmarshalJSON。
性能优化要点
- 用具体结构体代替 map[string]any:省掉装箱与字符串 key 开销,且类型安全。
- Decoder/Encoder 设置:UseNumber 保留精度;Encoder.SetEscapeHTML(false) 关闭 <>& 转义。
- 复用:json.Encoder 不并发安全,可放 sync.Pool 复用内部缓冲。
- 大整数与金额:float64 超过 2^53 会丢精度,改用 json.Number、string 或自建类型。
- 极热路径再考虑 jsoniter/easyjson 或手写编解码。
代码示例
type Order struct {
ID int64 `json:"id,string"` // 前端友好:数字串
User string `json:"user,omitempty"` // 空串不输出
Remark *string `json:"remark,omitempty"` // nil 才省略,能区分零值
secret string // 未导出:永远不参与
}
var buf bytes.Buffer
enc := json.NewEncoder(&buf)
enc.SetEscapeHTML(false)
_ = enc.Encode(order)
常见追问 / 记忆点
- 追问:time.Time 序列化出来是什么格式?——RFC3339 字符串,因为 time.Time 实现了 Marshaler/Unmarshaler。
- 追问:反序列化遇到未知字段会报错吗?——默认静默忽略;需要严格校验用 Decoder.DisallowUnknownFields。
- 记忆点:omitempty 是“零值省略”而非“空值省略”;要区分没填/填零值时用指针或自定义编解码。