安全提取结构体字段的 json tag 需分步校验:先确认 reflect.ValueOf(v).Kind() 为 reflect.Struct,再用 reflect.TypeOf(v).NumField() 控制循环范围,避免越界和零值 panic。

Go 语言里用 reflect 提取结构体字段的 tag 字符串,核心就一句话:先用 reflect.TypeOf 拿到类型,再用 Field 索引取字段,最后调用 Tag.Get。但实际写的时候,90% 的坑出在没校验零值、忽略 struct tag 的语法限制、或误把嵌入字段当顶层字段处理。
怎么安全地从 struct 字段拿到 json tag 值
别直接链式调用 reflect.TypeOf(v).Field(i).Tag.Get("json")——一旦 v 不是 struct、字段越界、或字段没定义 json tag,就会 panic 或返回空字符串。必须分步检查:
-
reflect.ValueOf(v).Kind()必须是reflect.Struct - 用
reflect.TypeOf(v).NumField()控制循环范围,别硬写i - 每次取
Field(i)后,先判断IsExported()(非导出字段无法反射访问) -
Tag.Get("json")返回空字符串不等于“没 tag”,可能是json:""或json:"-,omitempty",得结合Tag.Lookup判断是否存在 key
示例片段:
if t := reflect.TypeOf(v); t.Kind() == reflect.Struct {
for i := 0; i < t.NumField(); i++ {
f := t.Field(i)
if !f.IsExported() { continue }
if jsonTag, ok := f.Tag.Lookup("json"); ok && jsonTag != "-" {
// jsonTag 是完整字符串,如 "name,omitempty",需自行解析
}
}
}
Tag.Get 和 Tag.Lookup 的区别到底在哪
Tag.Get("json") 是「取值」操作:它会跳过 key 后的空格,提取双引号内的内容,但遇到 json:"-" 就返回空字符串;而 Tag.Lookup("json") 是「查键」操作:只要 tag 里声明了这个 key(哪怕值是 - 或空),就返回对应原始字符串,并返回 true。
立即学习“go语言免费学习笔记(深入)”;
- 想判断字段是否被显式禁用序列化?用
Tag.Lookup("json")+ 比对是否等于"-" - 想获取实际用于序列化的字段名?用
Tag.Get("json"),但注意它自动截掉,omitempty等选项 - 想完整解析 tag(比如同时读取
json和validate)?两个都得调,不能只靠Get
嵌入字段(anonymous field)的 tag 为什么取不到
嵌入字段本身没有名字,它的 tag 不属于外层 struct 的字段,而是属于那个嵌入类型的字段。比如:
type User struct {
Name string `json:"name"`
Info `json:"info"` // Info 是另一个 struct 类型
}
type Info struct {
Age int `json:"age"`
}
这时 User 的第二个字段是 Info 类型,Field(1).Tag.Get("json") 返回的是 "info"(即嵌入时写的 tag),不是 Info.Age 的 "age"。要递归读取嵌入字段内部的 tag,得手动展开 Field(1).Type,再遍历它的字段——reflect 不会自动扁平化嵌入结构。
- 如果只是想“展平”所有可导出字段(含嵌入),需要自己写递归函数,每层判断
Anonymous字段 - 别依赖
json包的默认行为反推逻辑:它内部做了字段合并,但reflect层面仍是分离的 - 嵌入字段的 tag 若为
`json:"-"`,只会屏蔽该嵌入字段本身,不影响其内部字段是否参与序列化
真正难的不是取 tag,而是决定什么时候该取、取完之后怎么拆解(比如 "name,omitempty,string" 里的多个选项),以及如何和实际序列化逻辑对齐。tag 字符串从来不是孤立存在,它总在某个库(json、gorm、validator)的解析规则下才有意义。


















