go的encoding/json健壮但需精准控制:字段必须导出且带json标签;小写字段被忽略;标签是映射契约;omitempty控制零值输出;"-"排除字段;未知字段默认静默丢弃,需disallowunknownfields()启用严格模式;时间、数字、空值需自定义处理;优先用结构体而非map[string]interface{}提升性能。

Go 标准库的 encoding/json 足够健壮,但默认行为容易导致字段丢失、类型错位或空值处理异常——关键不在“能不能用”,而在结构体定义和标签控制是否精准。
struct 字段必须导出且带 json 标签才能参与编解码
小写字母开头的字段(如 name string)在 JSON 编解码中会被完全忽略,无论是否加标签;大写字母开头是硬性前提。标签不是可选装饰,而是字段映射的契约。
-
json:"name"表示序列化为"name",反序列化时也只匹配该 key -
json:"name,omitempty"在值为零值(""、0、false、nil)时不输出该字段 -
json:"-"彻底排除该字段,连零值也不参与 - 嵌套结构体字段若未加
json标签,其内部字段仍可能被编码(取决于是否导出),但无法通过外层标签控制
json.Unmarshal 遇到未知字段默认静默丢弃,需显式启用严格模式
默认情况下,JSON 中多出的字段(比如后端加了新字段,客户端 SDK 未更新)会被直接跳过,不报错也不提示——这会让数据一致性问题难以发现。
- 使用
json.Decoder替代json.Unmarshal,调用DisallowUnknownFields() - 错误类型为
*json.UnsupportedTypeError或*json.InvalidUnmarshalError,但未知字段触发的是json.SyntaxError子类
decoder := json.NewDecoder(strings.NewReader(data)) decoder.DisallowUnknownFields() err := decoder.Decode(&v)
时间、数字、空值处理最容易踩坑
time.Time 默认按 RFC3339 序列化,但若 JSON 里是 Unix 时间戳(1717023456)或自定义格式("2024-05-30 14:23:11"),必须自定义 UnmarshalJSON 方法;float64 接收整数会成功,但反向用 int 接收浮点数会 panic;nil 指针字段反序列化为 null 时不会自动分配内存。
立即学习“go语言免费学习笔记(深入)”;
- 时间字段建议统一用
*time.Time,并在UnmarshalJSON中支持多种输入格式 - 整数优先用
int64,避免int在 32 位系统上溢出 - 需要区分“未设置”和“设为空”,用
*string、*int等指针类型,而非零值语义的普通类型
性能敏感场景慎用 map[string]interface{}
它看似灵活,实则带来三重开销:反射解析、类型断言、内存分配。对固定结构的 JSON,结构体解码快 3–5 倍,GC 压力更低。
- 仅在字段名动态(如配置项键名不可预知)、或需部分提取时才用
map[string]interface{} - 用
json.RawMessage延迟解析嵌套 JSON 字段,避免重复解码 - 批量处理时复用
json.Decoder实例,并提前bytes.Buffer或strings.Reader
var raw json.RawMessage err := json.Unmarshal(data, &raw) // 先拎出来 // 后续按需 json.Unmarshal(raw, &target)
最常被忽略的是:JSON 键名大小写与 Go 字段名大小写的映射关系,不是靠驼峰转换自动完成的——它只认 json 标签,没标签就按原字段名转小写,而 Go 字段名首字母大写是强制要求。漏标、错标、大小写不一致,是 80% 的解析失败根源。










