传入结构体变量得只读副本,须传指针再调用.Elem()才能写入;未导出字段不可读,仅能判断可访问性;Type用于元信息,Value用于读写;嵌套字段需递归处理;遍历前须确认结构体类型并用.NumField(),索引从0开始。
reflect.ValueOf() 获取结构体反射值直接传入结构体变量(非指针)会得到只读副本,字段修改无效;传入指针才能写入。多数场景建议用 reflect.ValueOf(&s),再调用 .Elem() 进入实际值。
reflect.Value 无法读取其值,仅能通过 .CanInterface() 和 .CanAddr() 判断可访问性reflect.TypeOf(s) 返回 reflect.Type,用于获取字段名、标签等元信息;reflect.ValueOf(s) 返回 reflect.Value,用于读写值.Field(i) 和 .Type().Field(i) 同步处理类型与值必须先确认是结构体类型,再用 .NumField() 获取字段数,否则 panic。字段索引从 0 开始,.Field(i) 返回 reflect.Value,.Type().Field(i) 返回 reflect.StructField。
type User struct {
Name string `json:"name"`
Age int `json:"age"`
}
u := User{Name: "Alice", Age: 30}
v := reflect.ValueOf(&u).Elem()
t := v.Type()
for i := 0; i < v.NumField(); i++ {
field := t.Field(i)
value := v.Field(i)
if !value.CanInterface() {
continue // 跳过不可访问字段(如未导出字段)
}
fmt.Printf("字段名: %s, 类型: %s, 值: %v, JSON标签: %s\n",
field.Name,
field.Type.String(),
value.Interface(),
field.Tag.Get("json"))
}
reflect.Value.Field() 对非结构体类型(如 int、string、*T)直接调用会 panic;对 nil 指针字段调用 .Elem() 同样 panic。必须逐层检查类型和有效性。
value.Kind() == reflect.Struct 判断是否为结构体,再遍历value.Kind() == reflect.Ptr,再判断 value.IsNil(),非 nil 才调用 .Elem()
value.Elem() 解包,再判断内部类型.Len() 或 .MapKeys() 会 panic,应先用 value.IsValid() 和具体 Kind 判断反射在运行时解析类型,开销显著高于静态访问。高频路径(如 HTTP 中间件、序列化 hot path)应避免;字段数量固定且已知时,手写展开更可靠。
Tag 排序而非索引顺序unsafe 或 go:linkname 等绕过反射的方案虽快,但破坏类型安全,仅限极少数底层库场景字段可访问性、嵌套深度、nil 安全、性能代价——这四点不同时验证,很容易在上线后触发 panic 或数据丢失。