Go 反射实战:用 reflect 写通用结构体转 map 与按 tag 校验

test12026-11-031 次阅读

反射能帮我们做什么

Go 是静态语言,大多数时候用接口和泛型就够了。但当你要写序列化、参数绑定、配置解析这类"对任意结构体都通用"的工具时,反射就派上用场。reflect 包能在运行时读出字段名、类型、tag,也能动态读取值。代价是失去编译期检查,且比直接访问慢,所以要把它用在真正需要通用性的地方。

把结构体转成 map

下面这个函数遍历结构体的导出字段,把字段名(或 json tag)当作 key,字段值当作 value,返回一个 map。常用于把请求体转成可打印的日志或转交泛型写入层。

func StructToMap(v interface{}) map[string]interface{} {
    out := make(map[string]interface{})
    rv := reflect.ValueOf(v)
    for rv.Kind() == reflect.Ptr {
        rv = rv.Elem()
    }
    if rv.Kind() != reflect.Struct {
        return out
    }
    rt := rv.Type()
    for i := 0; i < rt.NumField(); i++ {
        field := rt.Field(i)
        if !field.IsExported() {
            continue
        }
        key := field.Tag.Get("json")
        if key == "" || key == "-" {
            key = field.Name
        } else {
            key = strings.Split(key, ",")[0]
        }
        out[key] = rv.Field(i).Interface()
    }
    return out
}

按 tag 做必填校验

很多 Web 框架的校验器底层就是这个思路。我们定义一个 validate:"required" 的 tag,遍历字段时检查非空,收集所有错误一次返回,而不是遇到第一个就中断。

func Validate(v interface{}) []string {
    var errs []string
    rv := reflect.ValueOf(v)
    for rv.Kind() == reflect.Ptr {
        rv = rv.Elem()
    }
    rt := rv.Type()
    for i := 0; i < rt.NumField(); i++ {
        field := rt.Field(i)
        if field.Tag.Get("validate") != "required" {
            continue
        }
        fv := rv.Field(i)
        switch fv.Kind() {
        case reflect.String:
            if strings.TrimSpace(fv.String()) == "" {
                errs = append(errs, field.Name+" 不能为空")
            }
        case reflect.Int:
            if fv.Int() == 0 {
                errs = append(errs, field.Name+" 不能为 0")
            }
        }
    }
    return errs
}
type User struct {
    Name  string `json:"name" validate:"required"`
    Age   int    `json:"age"  validate:"required"`
    Email string `json:"email"`
}

func main() {
    u := User{Name: "", Age: 0, Email: "a@b.com"}
    if errs := Validate(u); len(errs) > 0 {
        fmt.Println("校验失败:", errs)
    }
}

性能与使用边界

反射调用比直接字段访问慢一到两个数量级。如果这段代码在热路径上(比如每个请求都跑),可以用代码生成在编译期产出一个类型专属的编码器,或者仅在启动时构建一次字段元数据缓存。经验法则:通用工具层用反射,业务热点路径尽量绕开。

小结

reflect 把"对任意结构体通用"变成可能,典型落点是序列化、校验、配置映射。写反射代码时记得处理指针和未导出字段,并且只在必要的通用层使用,避免把性能损耗带进请求主链路。

T

test1

文章作者

反射能帮我们做什么 Go 是静态语言,大多数时候用接口和泛型就够了。但当你要写序列化、参数绑定、配置解析这类"对任意结...

分类
技术
发布时间
2026-11-03
字数
约 2222 字
阅读
1 次