要获取结构体字段名、类型、值,须先用 reflect.TypeOf 获取类型信息,再用 reflect.ValueOf 获取值信息;遍历字段需基于 Value 的 NumField/Field 方法,且传入值必须为导出结构体(非指针或先 Elem 解引用),字段需导出才能访问值与 tag,修改值前须确保 Value 可寻址且可设置,反射性能低,不宜用于热路径。

用 reflect.TypeOf 和 reflect.ValueOf 获取结构体字段信息
Go 反射要拿到结构体字段名、类型、值,必须先用 reflect.TypeOf 拿类型信息,再用 reflect.ValueOf 拿值信息。两者不能混用:前者返回 reflect.Type,后者返回 reflect.Value,字段遍历必须基于 Value 的 NumField/Field 方法。
常见错误是直接对指针类型调 NumField,结果 panic:panic: reflect: NumField of non-struct type —— 因为 *T 是指针类型,不是 struct 类型。
- 确保传入的是结构体值(非指针),或先用
.Elem()解引用 - 字段必须是导出的(首字母大写),否则
Field返回零值且不可设 -
reflect.TypeOf(x).Name()对匿名结构体返回空字符串,需用.String()看完整类型描述
遍历字段时正确处理导出性与标签(tag)
反射无法访问未导出字段(小写开头),这是 Go 的语言限制,不是反射 API 的 bug。想读取 tag(如 json:"name"),要用 StructField.Tag.Get("json"),但前提是该字段已导出。
注意 Tag 是字符串,解析靠自己;标准库用 reflect.StructTag 提供 Get 方法,但不自动处理 quote 或空格——比如 `json:"user_name,omitempty"` 中的 omitempty 需手动切分。
立即学习“go语言免费学习笔记(深入)”;
- 用
field.Type.Kind() == reflect.Struct判断是否嵌套结构体,再递归处理 - 标签值里带空格或逗号时,
Tag.Get("json")返回完整字符串,别直接当布尔用 - 如果结构体字段是接口类型(
interface{}),field.Type是interface{},但field.Interface()才是真实值
修改字段值必须传地址且字段可寻址
用反射改结构体字段值,reflect.Value 必须是可寻址的(CanAddr() == true),通常意味着原始变量得是指针,且字段本身导出。否则调 SetXxx 会 panic:reflect: reflect.Value.SetString using unaddressable value。
典型错误写法:
type User struct { Name string }
u := User{"Alice"}
v := reflect.ValueOf(u).FieldByName("Name")
v.SetString("Bob") // panic!
正确做法:
u := &User{"Alice"}
v := reflect.ValueOf(u).Elem().FieldByName("Name")
if v.CanSet() {
v.SetString("Bob")
}
-
reflect.ValueOf(u).Elem()是关键:先取指针指向的值,才能寻址 - 永远在
SetXxx前检查CanSet(),它比CanAddr()更严格(还要求字段导出) - 对 int/float 等基本类型,用
SetInt/SetFloat64,别用Set传 interface{}
性能和适用边界:别在热路径用反射遍历结构体
反射比直接字段访问慢 10–100 倍,且编译器无法内联或优化。日常序列化(如 JSON)、ORM 映射、配置绑定等场景合理,但高频循环里逐字段反射读写就是反模式。
容易被忽略的一点:反射无法获取字段定义顺序以外的信息,比如 struct 字面量里的注释、默认值、是否必填——这些只能靠额外标记(如自定义 tag)或代码生成补足。
如果项目中大量出现 reflect.Value.FieldByName,建议评估是否该用 code generation(如 stringer 或自定义 go:generate 工具)预生成类型安全的访问函数。










