Go反射通过reflect.TypeOf和Field获取结构体字段,再用Tag.Get("json")读取原始标签字符串(如"name,omitempty"),空标签返回空字符串;导出字段才可读,需自行解析字段名与omitempty等选项。

Go反射怎么读取结构体的 json 标签
Go反射本身不解析JSON,它只负责从结构体字段上读取 json 标签字符串。真正做序列化/反序列化的是 encoding/json 包,而反射只是提供标签访问能力。
关键步骤是:用 reflect.TypeOf 拿到类型,再用 Field 获取字段,最后调用 Tag.Get("json") 提取值。
-
Tag.Get("json")返回的是原始字符串,比如"name,omitempty"或"-",需要自己解析逗号分隔的选项 - 如果字段没写
json标签,Tag.Get("json")返回空字符串(不是 panic) - 导出字段(首字母大写)才有标签可读;未导出字段即使写了标签,反射也拿不到
如何安全解析 json 标签里的 name 和 omitempty
直接对 Tag.Get("json") 的返回值做字符串切分容易出错,比如字段名含逗号、空格或引号时。标准做法是复用 encoding/json 内部的解析逻辑 —— 虽然没公开 API,但可以抄它的规则:
- 以第一个逗号为界,前面是字段名(去掉双引号),后面是选项列表
- 选项中
omitempty是唯一被标准库识别的布尔标记;其他如string是 hint,不是标签语法的一部分 - 字段名为
"-"表示忽略该字段,此时不应再解析后续内容
func parseJSONTag(tag string) (name string, omit bool) {
if tag == "-" {
return "", true
}
if i := strings.Index(tag, ","); i != -1 {
name = strings.Trim(tag[:i], `"`)
rest := tag[i+1:]
if strings.Contains(rest, "omitempty") {
omit = true
}
} else {
name = strings.Trim(tag, `"`)
}
return
}
为什么 json:"user_name" 反射读出来是 user_name,但 JSON 解码却能映射到 UserName 字段
因为 encoding/json 的匹配逻辑不是简单比对标签名,而是做了大小写不敏感的驼峰转换:
- 解码时,把 JSON 键
"user_name"自动转成"UserName"去匹配结构体字段名 - 这个转换只在
json包内部发生,反射层完全不知情 —— 它看到的永远是原始标签字符串 - 如果你手动用反射做映射(比如写通用 DTO 转换器),必须自己实现这套转换,不能只依赖
Tag.Get("json")
常见陷阱:嵌套结构体、匿名字段和指针字段的标签行为
反射读标签时,不会自动展开嵌套或穿透指针,必须逐层处理:
- 匿名字段(内嵌结构体)的
json标签只有在显式指定时才生效,否则默认继承外层字段名 -
*User类型的字段,反射拿到的是指针类型,需先用Elem()才能拿到User的字段和标签 - 切片或 map 中的元素类型,反射无法直接读其字段标签 —— 必须先取元素类型(如
sliceType.Elem()),再查字段 - 使用
json.RawMessage的字段,标签依然存在,但json包会跳过解析,反射层照常可读
encoding/json 包里那几百行解析逻辑 —— 而它并不暴露给你。










