要获取结构体字段名、类型、值,须先用 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() 看完整类型描述反射无法访问未导出字段(小写开头),这是 Go 的语言限制,不是反射 API 的 bug。想读取 tag(如 json:"name"),要用 StructField.Tag.Get("json"),但前提是该字段已导出。
注意 Tag 是字符串,解析靠自己;标准库用 r 提供 
Get 方法,但不自动处理 quote 或空格——比如 `json:"user_name,omitempty"` 中的 omitempty 需手动切分。
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() 更严格(还要求字段导出)SetInt/SetFloat64,别用 Set 传 interface{}反射比直接字段访问慢 10–100 倍,且编译器无法内联或优化。日常序列化(如 JSON)、ORM 映射、配置绑定等场景合理,但高频循环里逐字段反射读写就是反模式。
容易被忽略的一点:反射无法获取字段定义顺序以外的信息,比如 struct 字面量里的注释、默认值、是否必填——这些只能靠额外标记(如自定义 tag)或代码生成补足。
如果项目中大量出现 reflect.Value.FieldByName,建议评估是否该用 code generation(如 stringer 或自定义 go:generate 工具)预生成类型安全的访问函数。