Go 中处理 JSON 完全指南
Go JSON 全攻略:Unmarshal/Marshal 双向转换、struct tag 定制字段、常见反序列化坑、数据校验、自定义 Marshaler、第三方高性能库与 json/v2 实验版。一篇讲透。
本文依据官方文档整理,未执行运行验证或性能基准。代码片段展示局部用法,业务函数、数据和环境需按项目补齐;版本与配置以所引文档为准。
直接回答:Go 用标准库 encoding/json 处理 JSON——json.Unmarshal 解析进结构体,json.Marshal 序列化输出,struct tag 定制字段名与省略行为。常见坑:interface{} 丢类型、数字变 float64、时间格式不匹配;校验用 validator 库,高性能场景看 sonic/jsoniter,未来关注官方 json/v2。
双向转换基础
type User struct {
Name string `json:"name"`
Email string `json:"email,omitempty"`
Age int `json:"age,omitzero"` // Go 1.24+ 正确省略零值
}
var u User
if err := json.Unmarshal(payload, &u); err != nil {
return fmt.Errorf("decode user: %w", err)
}
data, err := json.Marshal(u)
if err != nil { return fmt.Errorf("encode user: %w", err) }
_ = data
流式场景(大文件、HTTP body)用 json.NewDecoder(r).Decode() / json.NewEncoder(w).Encode(),可避免先额外读完整个输入,但 Decode 单个大值仍会缓冲并分配该值;HTTP 入口应另限请求体大小,并检查是否还有第二个 JSON 值。
反序列化的常见坑
- interface{} 丢类型:解析到
any后数字全是float64,对象全是map[string]any——能用结构体就别用 any - 未知字段静默忽略:
decoder.DisallowUnknownFields()开启严格模式,API 边界防字段拼错 - 时间格式:RFC3339 之外格式要自定义 UnmarshalJSON
- null vs 缺省:对新建目标,指针通常都得到 nil,不能单靠指针区分两者;需自定义带 presence 标记的类型或先解析 map/json.RawMessage 检查键是否存在
校验
type Signup struct {
Email string `json:"email" validate:"required,email"`
Age int `json:"age" validate:"gte=18"`
}
// go-playground/validator
err := validate.Struct(s)
结构体 tag 声明规则,一行调用完成校验——别手写 if 链。
自定义编解码
实现 MarshalJSON()/UnmarshalJSON() 接管任意类型的 JSON 表现:自定义日期格式、敏感字段脱敏输出、兼容遗留格式都靠这两个接口。
性能选项
encoding/json 的反射有开销。热路径替代方案:sonic(字节,SIMD 加速)、jsoniter 等候选库需验证边界行为而非假设无差别替换。Go 1.25 引入实验性 encoding/json/v2,需 GOEXPERIMENT=jsonv2;不是本文 v1 示例的直接替代。实际收益由项目基准验证。
常见问题(FAQ)
Q:omitempty 为什么不省略空的 time.Time?
A:omitempty 只认"空值"(空串/nil/0/false),结构体不算。Go 1.24 用 omitzero(按 IsZero() 判断)解决。
Q:大整数 ID 反序列化丢精度?
A:解析进 any 时默认使用 float64,超过 2^53 的整数可能丢精度;结构体 int64 字段并不先转 float64。动态对象可用 Decoder.UseNumber;,string 要求输入本来就是带引号的数字字符串。
Q:什么时候用 json.Decoder 的 Token 流式解析?
A:GB 级 JSON 或只需少数字段的场景——逐个 token 处理可控制额外内存,但单个超大字符串、嵌套深度和调用方保留数据仍会占用内存,不能承诺严格恒定。代价是代码复杂度,一般项目用不上。
官方参考
资料核对日期:2026-09-29。