json.Marshal仅序列化首字母大写的导出字段,小写字段被忽略;需改名或用json tag控制,omitempty跳过零值,"-"完全忽略,time.Time默认RFC3339字符串,加string tag可输出带引号字符串,时间戳需自定义MarshalJSON。

json.Marshal 会忽略 struct 字段?检查导出规则和 tag
Go 的 json.Marshal 只序列化**首字母大写的导出字段**,小写字段默认被跳过,不是 bug,是语言设计使然。常见错误是定义了 type User struct { name string },结果输出空对象 {}。
解决办法只有两个:改字段名(Name string),或用 json tag 显式控制(即使字段已导出,tag 也能重命名或屏蔽):
type User struct {
Name string `json:"name"`
Email string `json:"email,omitempty"`
ID int `json:"-"`
}
-
omitempty表示零值字段不出现(注意:字符串空、数字 0、nil 切片都算零值) -
-表示完全忽略该字段,无论值是什么 - tag 中的 key 名区分大小写,
"Name"和"name"效果不同
反序列化时字段类型不匹配导致 silent fail 或 panic
json.Unmarshal 对类型错误很“宽容”:比如 JSON 里是 "123"(字符串),但目标字段是 int,它不会报错,而是尝试转换;失败时设为零值,且不返回 error —— 这是极易被忽视的隐患。
典型场景:API 返回的 ID 有时是数字、有时是字符串(后端不规范),而你用 ID int 接收,遇到字符串就变成 0,还查不出原因。
立即学习“go语言免费学习笔记(深入)”;
- 优先用指针字段(如
*int)+omitempty,能区分“没传”和“传了但转失败” - 对关键字段,手动校验解码后值是否合理(比如
if user.ID == 0 && user.RawMessage != nil) - 更健壮的做法:用
json.RawMessage延迟解析,或自定义UnmarshalJSON方法
嵌套结构体 + 空值处理:omitempty 不等于 “不传”
omitempty 只判断字段当前 Go 值是否为零值,跟 JSON 里有没有这个 key 完全无关。比如你传了 {"profile": {}},而 Profile *Profile 是 nil,那 omitempty 会让它消失;但如果你传了 {"profile": null},解码后 Profile 是 nil,再序列化回去,omitempty 依然生效 —— 看似一致,实则丢失了 “明确置 null” 的语义。
- 想保留 null,字段类型必须是
*T(指针),且不能加omitempty - 如果 API 要求某些字段必须存在(哪怕为 null),就别用
omitempty,靠业务逻辑保证初始值 - 注意:
map[string]interface{}和[]interface{}里的 nil 元素,在序列化时会被跳过,不是 bug,是 spec 行为
时间字段序列化成字符串还是 Unix 时间戳?得看需求定
time.Time 默认序列化为 RFC3339 字符串(如 "2024-05-20T14:23:18Z"),但很多老系统只认秒级时间戳(int64)。强行用 Unix() 转再塞进 struct 会破坏类型安全,也难维护。
- 标准做法:给 time 字段加自定义 tag,如
CreatedAt time.Time `json:"created_at,string"`,加上string就会走MarshalText,输出带引号的字符串(如"2024-05-20T14:23:18Z") - 要输出时间戳,必须实现
MarshalJSON方法,返回json.Number(strconv.FormatInt(t.Unix(), 10)) - 注意:加了
stringtag 后,反序列化也要求输入是字符串,传数字会直接 error
时间格式不是“选一个就行”,前后端约定、日志可读性、数据库兼容性都得一起看,改一处常要动三处。










