Go struct 标签
Go 结构体字段的元数据标签,通过反射读取,广泛用于 JSON 序列化、数据库映射和参数校验等场景。
#type / concept
#status / growing
#tech / dev
#resource / go
[!info] related notes
- 所属 MOC: Go 类型系统与抽象 MOC
- 前置概念: [[go-structs|Go 结构体]], [[go-reflect|Go 反射]]
- 并列概念: Go 嵌入与组合
Go struct 标签
一句话定义
struct tag 是附加在结构体字段上的元数据字符串,由 reflect 包在运行时读取,驱动 JSON 编解码、ORM 映射、参数校验等框架行为。
核心机制 / 工作原理
-
语法格式:标签是反引号包裹的原始字符串,写在字段类型之后。格式为一个或多个
key:"value"对,空格分隔:Name string `json:"name" db:"user_name" validate:"required"` -
通过反射读取:使用
reflect.Type.Field(i).Tag.Get("key")获取指定 key 的值。Lookup方法同时返回值和是否存在。 -
常用标签体系:
Key 来源 作用 jsonencoding/json控制 JSON 序列化字段名和行为 dbdatabase/sql, sqlx数据库列名映射 yamlgopkg.in/yaml.v3YAML 序列化 validatego-playground/validator参数校验规则 gormGORM ORM 数据库模型定义 mapstructuremapstructuremap 到 struct 的映射 -
json 标签详解:
json:"name"— 指定 JSON 键名为namejson:"-"— 该字段不参与 JSON 编解码json:",omitempty"— 零值时省略json:"name,omitempty"— 组合使用json:",string"— 将数值编码为 JSON 字符串
-
标签是编译后不变的常量:标签值在编译时确定,嵌入到二进制中,运行时通过反射访问。不能使用变量或动态生成。
最小例子 / 最小场景
package main
import (
"encoding/json"
"fmt"
"reflect"
)
type User struct {
ID int `json:"id" db:"id"`
Name string `json:"name" db:"user_name" validate:"required"`
Email string `json:"email,omitempty" db:"email"`
Password string `json:"-" db:"password"` // JSON 中不暴露
Age int `json:"age,string" db:"age"` // JSON 中编码为字符串
createdAt string // 未导出字段, 反射无法读到 tag
}
func main() {
u := User{ID: 1, Name: "Alice", Password: "secret", Age: 30}
// JSON 序列化
data, _ := json.Marshal(u)
fmt.Println(string(data))
// {"id":1,"name":"Alice","age":"30"} — Password 被跳过, Email 因 omitempty 省略, Age 变成字符串
// 反射读取 tag
t := reflect.TypeOf(User{})
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
jsonTag, _ := field.Tag.Lookup("json")
dbTag, _ := field.Tag.Lookup("db")
fmt.Printf("%s -> json=%q, db=%q\n", field.Name, jsonTag, dbTag)
}
// ID -> json="id", db="id"
// Name -> json="name", db="user_name"
// ...
}
为什么重要
- 框架驱动的核心机制:Go 生态中的 web 框架、ORM、配置解析、参数校验全部依赖 struct tag,不理解 tag 就无法有效使用这些工具。
- 数据契约的声明式表达:通过 tag 在一个地方声明 JSON 键名、数据库列名、校验规则,比在每个业务函数中手动处理更清晰、更不容易出错。
- API 文档生成:Swagger/OpenAPI 工具链(如 swaggo/swag)通过读取 struct tag 和注释自动生成接口文档。
边界与易混淆点
- tag 只能是字符串常量:不能用
const变量作为 tag 值(json:MyConst是非法的),也不支持字符串插值。 - 未导出字段的 tag 不可见:
reflect无法读取未导出字段的 tag,因此createdAt string \db:“created_at”“ 中的 tag 会被忽略。 - 多个框架共存时注意冲突:同一个字段上多个 tag 用空格分隔(不是逗号),如
json:"name" db:"name"。如果某个框架的 tag 值本身就含空格,需要确认该框架的解析规则。 omitempty对零值的理解:数值 0、空字符串""、false、nil指针、空 slice/map 都是零值,都会被omitempty省略。如果 0 是有效值,需要考虑用指针*int或自定义类型。- 反射读取 tag 有性能开销:热路径中应缓存 tag 解析结果,而非每次调用都通过反射读取。