结构体反射 - 运行时结构体操作
结构体反射允许在运行时检查和操作结构体字段、标签和方法。这是实现 ORM、序列化、验证等框架的基础技术。
📌 核心概念
🏗️
字段访问
NumField/Field
reflect.Value
🏷️
Struct Tag
标签解析
Tag.Get()
✏️
字段修改
可寻址值
CanSet()
🔍
类型检查
Kind/Type
reflect.Kind
字段访问
📝 访问结构体字段
package main
import (
"fmt"
"reflect"
)
type Person struct {
Name string `json:"name"`
Age int `json:"age"`
City string `json:"city,omitempty"`
}
func main() {
p := Person{Name: "Alice", Age: 30, City: "Beijing"}
// 获取 reflect.Value
v := reflect.ValueOf(p)
// 字段数量
fmt.Printf("NumField: %d\n", v.NumField())
// 访问字段
for i := 0; i < v.NumField(); i++ {
field := v.Field(i)
fmt.Printf("Field %d: %v = %v\n", i,
v.Type().Field(i).Name, field.Interface())
}
// 通过字段名访问
nameVal := v.FieldByName("Name")
fmt.Printf("Name: %v\n", nameVal.String())
ageVal := v.FieldByName("Age")
fmt.Printf("Age: %v\n", ageVal.Int())
}
⚠️ 字段访问注意
- 导出字段: 只能访问大写开头的导出字段
- 指针解引用: 如果是指针需要先 Elem()
- CanSet: 修改前检查字段是否可设置
Struct Tag 解析
📝 解析 Struct Tag
package main
import (
"fmt"
"reflect"
"strings"
)
type User struct {
ID int `json:"id" db:"user_id" validate:"required"`
Username string `json:"username" db:"username" validate:"min=3"`
Email string `json:"email" db:"email" validate:"email"`
Password string `json:"-" db:"password"`
}
// 解析单个 Tag
func parseSingleTag() {
var u User
t := reflect.TypeOf(u)
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
tag := field.Tag
fmt.Printf("Field: %s\n", field.Name)
fmt.Printf(" JSON: %s\n", tag.Get("json"))
fmt.Printf(" DB: %s\n", tag.Get("db"))
fmt.Printf(" Validate: %s\n", tag.Get("validate"))
fmt.Println()
}
}
// 解析 Tag 选项 (如 omitempty)
func parseTagOptions(tagName string) (string, []string) {
parts := strings.Split(tagName, ",")
if len(parts) == 0 {
return "-", nil
}
return parts[0], parts[1:]
}
func main() {
parseSingleTag()
// 解析选项
name, opts := parseTagOptions("username,omitempty")
fmt.Printf("Name: %s, Options: %v\n", name, opts)
}
修改字段值
📝 通过反射修改字段
package main
import (
"fmt"
"reflect"
)
type Config struct {
Host string
Port int
}
func main() {
cfg := Config{Host: "localhost", Port: 8080}
// ❌ 错误:不能修改非指针的值
v := reflect.ValueOf(cfg)
fmt.Printf("CanSet: %v\n", v.Field(0).CanSet()) // false
// ✅ 正确:使用指针
v = reflect.ValueOf(&cfg).Elem() // Elem() 解引用
// 修改字段
if v.Field(0).CanSet() {
v.Field(0).SetString("127.0.0.1")
}
if v.Field(1).CanSet() {
v.Field(1).SetInt(9000)
}
fmt.Printf("Config: %+v\n", cfg)
// Config: {Host:127.0.0.1 Port:9000}
}
// 通用字段设置函数
func SetField(obj interface{}, fieldName string, value interface{}) error {
v := reflect.ValueOf(obj)
if v.Kind() != reflect.Ptr {
return fmt.Errorf("obj must be a pointer")
}
v = v.Elem()
field := v.FieldByName(fieldName)
if !field.CanSet() {
return fmt.Errorf("field %s cannot be set", fieldName)
}
field.Set(reflect.ValueOf(value))
return nil
}
实用模式
1. JSON 序列化
📝 简化版 JSON 序列化
func ToJSON(v interface{}) string {
val := reflect.ValueOf(v)
if val.Kind() == reflect.Ptr {
val = val.Elem()
}
var parts []string
for i := 0; i < val.NumField(); i++ {
field := val.Type().Field(i)
tag := field.Tag.Get("json")
if tag == "-" {
continue
}
name := field.Name
if tag != "" {
name = strings.Split(tag, ",")[0]
}
value := val.Field(i)
parts = append(parts, fmt.Sprintf(`"%s":%v`, name, value.Interface()))
}
return "{" + strings.Join(parts, ",") + "}"
}
2. 数据库映射
📝 结构体到数据库列映射
func GetColumnNames(v interface{}) []string {
t := reflect.TypeOf(v)
if t.Kind() == reflect.Ptr {
t = t.Elem()
}
var columns []string
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
dbTag := field.Tag.Get("db")
if dbTag == "-" {
continue
}
if dbTag != "" {
columns = append(columns, dbTag)
} else {
columns = append(columns, field.Name)
}
}
return columns
}
最佳实践
✅ 结构体反射使用建议
- 指针传递: 修改字段必须传指针
- CanSet 检查: 修改前检查是否可设置
- 缓存 Type: 避免重复获取 Type 信息
- 优先泛型: Go 1.18+ 优先使用泛型