结论:encoding/json 高频坑四类:1)omitempty 只省略零值(0、""、nil、false),不省略"业务上的空",且对结构体零值无效;2)未导出字段被静默忽略,导出但忘写 tag 时默认用字段名(大小写敏感匹配、大小写不敏感回退);3)interface{} 解码数字一律变 float64,大整数丢精度;4)time.Time 反序列化要求 RFC3339,自定义格式要自己实现 Unmarshaler。
展开:对策逐个给:1)需要区分"零值"和"未传"用指针字段(*int),nil 即未传——API 部分更新(PATCH)的标准做法;2)内嵌结构体(匿名成员)的字段被提升,内嵌字段加 tag 反而整体降级为普通字段;两个提升字段同名同层级时双方都被丢弃,无编译错误,最难排查;3)精度问题用 json.Number(decoder.UseNumber())或定义 json:"id,string" 让数字走字符串;4)自定义时间格式实现 MarshalJSON/UnmarshalJSON。易错点:map 解码时多余键被丢弃、缺失键保持零值——"宽松"是默认行为,严格校验要 DisallowUnknownFields;Marshal 遇 channel/func/循环引用直接报错。
type PatchReq struct {
Name *string `json:"name,omitempty"` // nil = 未提供,"" = 显式清空
}
dec := json.NewDecoder(r.Body)
dec.DisallowUnknownFields() // 严格模式
var v map[string]json.Number
dec.UseNumber() // 保精度
追问方向:jsoniter/sonic 等高性能库的差异(SIMD、JIT)、Go 1.25 讨论的 json/v2 解决哪些历史包袱。