struct tag 是 Go 中用于序列化、反射等的底层字符串约定,语法为 key:"value",多 tag 用空格分隔;错写不报错但运行时失效;omitempty 按字段零值判断省略,对指针和结构体行为特殊;不同库 tag key 互不干扰,应避免手动解析而依赖对应库处理。

Go 语言的 struct tag 不是用来“教程收藏”的,而是解决序列化、反射、校验等实际问题时绕不开的底层约定——写错一个空格或引号,json.Unmarshal 就静默失败,gorm 可能连字段都映射不上。
struct tag 的语法本质是字符串,不是 Go 语法的一部分
它只是写在 struct 字段声明后的一串带双引号的字符串,被 reflect.StructTag 解析。编译器不校验它的格式,错写了也不会报错,但运行时相关库(如 encoding/json)会直接忽略整个 tag 或跳过字段。
- 正确写法:
json:"name,omitempty" db:"name"—— key 和 value 用冒号分隔,多个 tag 用空格分隔,value 必须是双引号包裹的字符串 - 常见错误:
json:name(缺冒号)、json:"name",db:"name"(逗号导致整个 tag 被视为无效)、json:'name'(单引号不合法) - 空格敏感:
json:"name,omitempty" db:"id"中两个 tag 之间的多个空格没问题,但json: "name"(冒号后有空格)就非法
json tag 的 omitempty 是最常误用的点
omitempty 控制的是「零值序列化时是否省略字段」,但它判断的是字段类型的零值,不是 nil 指针或空结构体本身是否为 nil。
-
Age int `json:"age,omitempty"`:当Age == 0时,该字段不出现在 JSON 中 -
Name *string `json:"name,omitempty"`:只有当Name == nil才省略;如果Name指向一个空字符串"",它仍会被序列化为"name": "" - 嵌套结构体:
User User `json:"user,omitempty"`—— 即使User是零值(所有字段都是零),它也不会被省略,因为结构体类型没有“nil”概念;omitempty对 struct 类型基本无效
不同库对同一 tag key 的行为可能完全不同
同一个 json:"xxx" 在 encoding/json 和 github.com/go-playground/validator/v10 里互不影响,但像 gorm:"column:name" 和 sqlx:"name" 都想用 db: 就容易冲突。
立即学习“go语言免费学习笔记(深入)”;
-
json、xml、yaml是标准库定义的 key,语义统一 - 第三方库通常用自己名字作 key:
gorm、validate、mapstructure,互不干扰 - 别强行复用 key:比如把
validate:"required"写成json:"required",后者完全无意义 - 工具链依赖 tag:VS Code 的 Go 插件、
go vet都不会检查 tag 内容,只能靠你手写时盯紧
想动态读取或生成 tag?别硬解析字符串
直接用 reflect.StructTag.Get("json") 获取原始字符串,再手动 split 冒号和逗号,极其脆弱。正确做法是交给对应库的解析逻辑处理。
- 要取 JSON 字段名?用
json.Marshal+json.Unmarshal反推,或借助github.com/mitchellh/mapstructure这类已验证的解析器 - 做字段校验?直接调
validate.Struct(),它内部已完整处理了validatetag 的解析和嵌套 - 自定义 tag 解析逻辑?先确认
reflect.StructTag.Lookup("yourkey")返回非空,再用strings.Trim去首尾空格,别自己写正则去拆引号
tag 看似简单,但它是连接结构体定义与运行时行为的关键胶水——出问题时既不报错也不警告,只在数据没按预期出现时才暴露。最稳妥的做法是:每次加新 tag,立刻写个最小测试用例跑一遍序列化/反序列化流程。


















