Go 的 JSON 序列化需注意三要点:字段必须首字母大写才能导出;omitempty 仅对零值生效,不区分空字符串与空白;time.Time 默认序列化为时间戳,需自定义类型实现 MarshalJSON 输出 RFC3339 字符串。

Go 的 json.Marshal 和 json.Unmarshal 不是“写完就能用”的黑盒,字段导出规则、结构体标签、空值处理这三处不注意,90% 的 JSON 序列化问题就出在这儿。
结构体字段必须首字母大写才能被序列化
小写字段在 json.Marshal 时直接被忽略,连 omitempty 都没机会生效——这不是 bug,是 Go 的导出规则强制要求。
常见错误现象:json.Marshal 返回空对象 {} 或缺失字段;调试时打印结构体值正常,但转成 JSON 就“消失”。
- 确保所有要参与 JSON 编码的字段名首字母大写(如
Name、Email) - 如果必须保留小写字段名(比如对接 legacy API),用结构体标签显式映射:
json:"user_id" - 嵌套结构体同理:内部结构体字段也必须可导出,否则整个嵌套字段为
null或被跳过
struct tag 中的 omitempty 只对零值生效,不是“空字符串”或“nil 指针”
omitempty 的判定逻辑是:字段值等于其类型的零值(""、0、false、nil 等),而非语义上的“空”。它不区分 "" 和 " ",也不管指针是否指向有效值。
立即学习“go语言免费学习笔记(深入)”;
使用场景:API 请求体中省略未填写的可选字段;响应中隐藏默认值。
-
Age int `json:"age,omitempty"`:当Age == 0时字段被剔除(但 0 是合法年龄,慎用) -
Name *string `json:"name,omitempty"`:只有Name == nil才剔除;若Name指向空字符串"",字段仍存在且值为"" - 切片/映射为空(
[]int{}、map[string]int{})也会被omitempty剔除
时间类型默认序列化为 float 秒数,不是 ISO8601 字符串
Go 的 time.Time 在 JSON 中默认被编码为 Unix 时间戳(float64 秒数),和前端 JS 的 Date 或多数 REST API 期望的字符串格式不兼容。
错误现象:json.Marshal 输出 {"created_at":1718352000.123},而接口文档要求 "2024-06-15T08:00:00Z"。
- 最简方案:定义自定义类型并实现
MarshalJSON方法 - 示例:
type ISOTime time.Time func (t ISOTime) MarshalJSON() ([]byte, error) { return []byte(`"` + time.Time(t).Format(time.RFC3339) + `"`), nil } - 避免全局替换
time.Time;仅在需要字符串格式的字段上使用该类型
反序列化时字段类型不匹配会静默失败或 panic
json.Unmarshal 对类型不一致的容忍度极低:数字字段填了字符串、布尔字段传了 "true"(而非 true)、嵌套对象填了数组——这些都不会自动转换,而是设为零值或报错。
常见错误现象:字段始终为零值、json: cannot unmarshal string into Go struct field X of type int、部分字段解析成功但关键字段丢失。
- 严格按 API 文档约定字段类型定义结构体,别靠猜测
- 对可能变化的字段(如后端有时返回数字、有时返回字符串),用
interface{}+ 运行时类型判断,或封装自定义UnmarshalJSON - 启用
json.Decoder.DisallowUnknownFields()捕获多余字段,避免静默忽略拼写错误
真正麻烦的从来不是语法,而是结构体定义和上下游数据契约之间的那层薄纸——字段名大小写、时间格式、零值语义、类型边界,每一条都得对齐。写完 json.Marshal 别急着提交,先用真实 payload 跑一遍 round-trip 测试。


















