JSON标签是编译期固化的字段契约,影响encoding/json序列化:字段必须导出才生效;omitempty仅对内置零值生效;string tag仅作用于未实现Marshaler的数字类型;多tag顺序无关但语义叠加需谨慎;反引号不可误写为双引号。

json 标签不是装饰器,也不是运行时才生效的注解——它是一份编译期就固化下来的字段级契约,直接参与 encoding/json 包的序列化逻辑。用错、漏用或误解它的行为,会导致字段丢失、类型错乱、空值处理异常等静默问题。
为什么 json:"name" 不生效?先看字段是否导出
Go 的 JSON 序列化根本不会碰私有字段(首字母小写),哪怕你写了 json:"token" 也没用。
-
Token string→ 可导出,json:"token"生效 -
token string→ 不可导出,json:"token"完全被忽略,Marshal 后无该字段
这是第一道门,比标签解析早得多。反射调用 reflect.Value.CanInterface() 会直接返回 false,后续所有 tag 解析逻辑都不会触发。
json:",omitempty" 的零值判断很严格
它只认 Go 内置类型的“零值”:0、""、nil、false。自定义类型或实现了 json.Marshaler 接口的类型(比如 time.Time)不走这套逻辑。
-
CreatedAt time.Time `json:"created_at,omitempty"`→ 即使是零时间time.Time{},也不会被省略,因为time.Time自带MarshalJSON() -
Count int `json:"count,omitempty"`→Count: 0会被省略 -
Status *string `json:"status,omitempty"`→nil指针会被省略;但&""不会被省略
json:",string" 不适用于所有数字类型
这个 tag 只对整型(int, int64 等)和浮点型(float64)起作用,前提是它们没实现 json.Marshaler。
立即学习“go语言免费学习笔记(深入)”;
-
Price float64 `json:"price,string"`→ 输出"price": "19.99" -
Amount decimal.Decimal `json:"amount,string"`→ 无效,因为decimal.Decimal通常实现了MarshalJSON(),会绕过 tag -
ID int `json:"id,string"`→ 有效,但注意反序列化时需确保输入是字符串格式,否则Unmarshal报json: cannot unmarshal string into Go struct field
多个 tag 共存时顺序无关,但语义叠加要小心
json tag 的值是一个空格分隔的字符串,各选项之间没有先后依赖。但组合使用时容易忽略隐含约束:
-
json:"name,omitempty,string"→ 先转字符串,再判断是否为零值(即空字符串"")才省略 -
json:"-"→ 优先级最高,直接跳过该字段,其他选项全部失效 -
json:"created_at,omitempty"和db:"created_at"可共存,互不影响,各自被对应包读取
真正容易被忽略的是:反引号必须用 `,不能用 " 或 ';一旦误写成双引号,转义字符(如 \n)会被解析,导致 tag 字符串损坏,而编译器不报错——错误只在运行时暴露,且难以定位。


















