Go反射无法修改结构体字段名,字段名在编译期固定;json tag仅影响序列化键名,可通过reflect.StructField.Tag.Get("json")读取并解析别名。

struct tag 里的 json 字段不是字段名,改它不改变反射获取的字段名
很多人以为给结构体字段加 json:"user_name" 就等于“重命名了字段”,其实不是。Go 的反射(reflect.StructField.Name)永远返回源码里写的原始名字,比如 UserName;json tag 只影响 encoding/json 包序列化/反序列化时的键名,和反射无关。
所以:想通过反射“修改字段名”,本质是做不到的——字段名在编译期就固定了,运行时不可变。
常见错误现象:
- 用 reflect.Value.Field(i).Name() 拿到 "UserName",但期望它返回 "user_name"
- 尝试用 reflect.StructTag.Set 去改 tag(会 panic,tag 是只读字符串)
真正能做的:用反射读取 json tag 并映射到自定义名称
如果你的目标是“根据 JSON 别名做字段处理”(比如生成 API 文档、构建动态查询条件),应该读取 tag,而不是试图改名。
- 用
reflect.TypeOf(t).Field(i).Tag.Get("json")获取 tag 值,例如"user_name,omitempty" - 手动切分:用
strings.SplitN(tagValue, ",", 2)[0]提取真实别名(去掉omitempty等修饰) - 注意空 tag:如果字段没写
json:,Get("json")返回空字符串,不是"UserName" - 别名可能含
-(忽略字段),此时应跳过该字段,而非当作名字使用
示例:
立即学习“go语言免费学习笔记(深入)”;
type User struct {
UserName string `json:"user_name"`
Age int `json:"age,omitempty"`
ID int `json:"-"`
}
// 反射遍历时,ID 字段的 json tag 是 "-",不是 "id"想统一控制 JSON 键名?别靠反射改名,用自定义 MarshalJSON
如果业务要求所有字段强制小写下划线(如 user_name),又不想每个字段都手写 tag,更可靠的方式是实现 json.Marshaler 接口。
- 避免反射操作 tag 的繁琐逻辑和边界 case(嵌套、指针、匿名字段等)
- 性能更可控:一次遍历 + 字符串转换,比反复调用
reflect.StructField.Tag.Get更快 - 兼容性好:不影响其他包对 struct 的反射使用(比如 ORM、validator)
- 注意:自定义
MarshalJSON后,json.Unmarshal不会自动同步,需配对实现UnmarshalJSON
第三方库如 mapstructure 或 gjson 处理别名时,底层仍是读 tag
很多工具(比如把 map[string]interface{} 解析进 struct)依赖 json tag 做字段匹配。它们内部就是调用 reflect.StructField.Tag.Get("json"),然后 fallback 到字段名。
- 如果你看到某个库“支持别名但不生效”,先检查 tag 是否拼错(比如写成
jsonn:) - 注意大小写:Go struct 字段必须首字母大写才可被反射导出,否则 tag 再对也读不到
- 嵌套 struct 的 tag 不会自动继承,每一层都要单独声明
- 用
go vet或staticcheck可捕获无效 tag(如重复 key、非法字符)
真正麻烦的从来不是怎么读 tag,而是字段名和 JSON 键名长期不一致导致的维护成本——这点反射帮不上忙,得靠约定或代码生成。










