Go struct 标签
结构体字段上的元数据字符串;encoding/json 等通过反射读取,驱动序列化、校验与 ORM 映射。
[!info] 关联笔记
- 所属 MOC:类型与抽象 · 学习路线
- 前置概念:struct 与方法、反射
- 后续概念:校验、嵌入与组合
- 相关:API 约定
- 实战:User 结构体时间字段、可空时间 *time.Time
Go struct 标签
这个概念为什么会出现
结构体字段在 Go 里首先是内存布局与类型信息。但工程还需要不污染领域类型的元数据:
- JSON 字段名与 omitempty
- DB 列名
- 校验规则
- 配置库的 key
若为每种格式再写一套平行结构体,重复且易漂。struct tag 把约定字符串贴在字段旁,由库在运行时用反射读取。语言只规定标签是字符串与惯用 key:"value" 形式;语义由各库定义。
[!abstract] 一句话理解 struct tag 是字段声明上的只读字符串元数据;
reflect.StructTag按 key 解析;encoding/json等标准库与生态库据此决定编解码与映射行为。
最小可运行示例
先把示例放进业务场景,再看代码:
场景:用户 API 响应 DTO,密钥绝不能出站
GET /me 返回用户资料 JSON:
- 字段名要是前端约定的
id/name(不是 Go 的ID/Name) - 邮箱为空时不要输出
"email":"" - 内存里有
Token用于服务端会话,序列化时必须丢掉
struct tag 就是贴在字段上的元数据,给 encoding/json 等库通过反射读取。
package main
import (
"encoding/json"
"fmt"
"reflect"
)
// UserDTO:对外 JSON 形状由 tag 描述,而不是字段名本身。
type UserDTO struct {
ID int64 `json:"id"`
Name string `json:"name"`
Email string `json:"email,omitempty"` // 空则省略
Token string `json:"-"` // 永不输出
}
func main() {
u := UserDTO{ID: 1, Name: "Ada", Token: "secret"}
// Email 为零值 "" → omitempty 生效,JSON 里没有 email
// Token 有值,但 json:"-" 仍被忽略
b, err := json.Marshal(u)
if err != nil {
panic(err)
}
fmt.Println(string(b)) // {"id":1,"name":"Ada"}
// 调试/写通用库时:用反射读出 tag 原文
t := reflect.TypeOf(u)
f, _ := t.FieldByName("Email")
fmt.Println("email tag:", f.Tag.Get("json")) // email,omitempty
}
建议运行:
go run .
期望输出:
{"id":1,"name":"Ada"}
email tag: email,omitempty
结合场景再看三个关注点
- tag 是给库看的元数据,语言本身不解释
json含义。 json:"-"防泄漏;密钥、内部 token 默认应忽略。omitempty只影响编码是否输出零值;标签必须用反引号字符串。
核心概念与准确模型
语法
Field Type `key:"value" other:"x,y"`
- 反引号原始字符串
- 多个
key:"value"以空格分隔 - 值里可用逗号表达选项(由具体库解释)
Spec — Tag 规定 tag 是可选字符串字面量;解析惯例见 reflect.StructTag。
反射 API
tag := field.Tag // reflect.StructTag
v := tag.Get("json") // 无则 ""
v, ok := tag.Lookup("json") // 区分缺失与空值
Get 对格式错误可能返回空;生产库应健壮解析。
标准库:json 标签(最常用)
| 片段 | 含义 |
|---|---|
json:"name" | 字段名 |
json:"-" | 忽略 |
json:",omitempty" | 空值省略(名仍默认) |
json:"name,omitempty" | 改名 + 省略 |
json:",string" | 部分数值等按 JSON 字符串编解码 |
“空值”对 bool/数字/字符串/指针/切片等定义不同,见文档。
可见性
只有导出字段参与 encoding/json 默认编解码。标签不能让小写字段被 json 包导出。
嵌入字段
嵌入 struct 的字段会“提升”;标签与冲突规则由具体编解码器处理。匿名字段本身也可带 tag。
其它常见 key(生态)
| key | 典型用途 |
|---|---|
xml | encoding/xml |
yaml | yaml 库 |
db / gorm | SQL 映射 |
validate | 校验器 |
mapstructure | map→struct |
form / query | HTTP 绑定 |
不要假设所有 key 语义统一;以对应库文档为准。
性能与设计边界
每次反射读 tag 有成本;热路径库会缓存类型描述。领域核心逻辑不应依赖 tag 才能正确——tag 是适配层配置。
边界情况
- 标签拼写错误:静默使用默认字段名,难发现。
- omitempty 与 false/0:合法零值被省略导致 API 歧义。
- 指针 vs 值:用
*T区分“未设置”与零值。 - 冲突字段名:多字段映射同一 JSON 名行为依赖库。
- 自定义类型:需
MarshalJSON/UnmarshalJSON时 tag 不够用。 - 安全:
json:"-"防敏感字段泄漏;测试覆盖序列化快照。
常见误区
[!warning] 常见误区:tag 是语言强制模式 错误:以为编译器理解
validate:"required"。
正确:只是字符串;无库读取则无效果。
[!warning] 常见误区:给未导出字段加 json tag 期望生效 错误:
name string \json:“name”“。
正确:字段名首字母大写,或手写编解码。
[!warning] 常见误区:用 tag 堆砌全部业务规则 错误:几十个 validate 规则取代领域校验。
正确:边界用 tag/校验库;核心不变量用代码。
[!warning] 常见误区:手写解析不遵循 StructTag 格式 错误:自创无法用
Get的格式。
正确:遵循key:"value"空格分隔惯例。
工程实践
- API DTO 与领域模型分离,tag 留在 DTO。
- 敏感字段默认
json:"-"或显式 DTO。 - 契约测试:黄金 JSON 文件防 tag 误改。
- 生成:protobuf/openapi 生成 struct 时审 tag。
- 统一 key 字典,避免
json/Json混用。 - 自定义 tag 写清文档与
Lookup错误处理。 - 与嵌入:明确提升字段的 JSON 形状。
// 可选字段常用指针
type Patch struct {
Name *string `json:"name,omitempty"`
}
可验证实验
实验 1:omitempty
比较 Email:"" 与有值时的 JSON。
实验 2:json:"-"
确认 Token 不出现。
实验 3:反射 Lookup
对缺失 key 与 key:"" 区分 ok。
实验 4:未导出字段
小写字段带 tag,观察是否出现在 JSON。
实验 5:自定义 MarshalJSON
与 tag 共存时谁优先(方法优先于默认 tag 逻辑)。
本节总结
- 本质:字段级字符串元数据 + 反射消费。
- 关键规则:导出字段、库定义语义、
Get/Lookup、json 选项。 - 最易错:拼写静默失败、omitempty 零值、未导出字段、安全泄漏。
- 下一步:反射 原理;校验 实践。
自测题
概念题
- 谁解释
validate:"required"的含义? json:"-"做什么?- 为什么 tag 必须是字符串字面量?
代码推理题
type T struct {
N int `json:"n,omitempty"`
}
json.Marshal(T{})
输出是什么?为何?
工程思考题
同一结构体既要 JSON 对外又要 SQL 扫描,如何组织 tag 与类型?
参考答案
展开
- 校验库在运行时,不是 Go 编译器。
- 编解码时忽略该字段。
- 语法上 tag 是可选 string lit;编译期固定进类型信息。
代码题:{}——N为零值且 omitempty 省略。
工程题:可一结构多 tag;或 DTO/DAO 分离避免 json 与 db 耦合;扫描用sql.Null*或指针表达 NULL。
延伸阅读与资料来源
| 资料 | 类型 | 支撑 |
|---|---|---|
| Spec — Tag | 规范 | 语法 |
| reflect.StructTag | 标准库 | 解析 |
| encoding/json | 标准库 | json tag 语义 |
| encoding/xml | 标准库 | xml tag |
| Effective Go — Structs | 文档 | 结构体风格 |
笔记元信息
- 建议文件名:
go-struct-tags.md - 所属阶段:类型与互操作
- 建议下一篇:Go 反射
- 本篇状态:已深化