Go语言的反射机制允许程序在运行时检查类型和变量信息,其中获取结构体字段名称和元数据是反射的常见使用场景,很多业务场景中需要根据结构体字段信息完成序列化、数据校验等逻辑。

反射获取结构体字段的核心基础
要使用反射操作结构体,首先需要了解reflect.Type和reflect.Value两个核心类型。reflect.Type用于描述类型的元数据,reflect.Value用于描述值的运行时信息。获取结构体字段的相关信息主要通过reflect.Type的Field系列方法实现。
需要注意,只有传入反射的类型是结构体类型时,才能调用获取字段的方法,否则会触发panic。如果传入的是结构体指针,需要先通过Elem()方法解引用得到结构体类型。
类型校验与指针解引用示例
package main
import (
"fmt"
"reflect"
)
type User struct {
Name string `json:"name" validate:"required"`
Age int `json:"age" validate:"min=1,max=120"`
}
func main() {
u := User{Name: "张三", Age: 20}
// 获取反射类型
t := reflect.TypeOf(u)
// 如果是指针类型,解引用
if t.Kind() == reflect.Ptr {
t = t.Elem()
}
// 校验是否为结构体类型
if t.Kind() != reflect.Struct {
fmt.Println("传入的不是结构体类型")
return
}
fmt.Println("类型校验通过,是结构体类型")
}
获取结构体字段名称
获取字段名称有两种常用方式,一种是遍历所有字段获取全部名称,另一种是根据索引或字段名获取单个字段信息。
遍历所有字段名称
可以通过NumField()方法获取结构体的字段总数,再通过Field(i)方法传入索引获取第i个字段的reflect.StructField信息,其中Name属性就是字段名称。
func printAllFieldNames(u interface{}) {
t := reflect.TypeOf(u)
// 处理指针情况
if t.Kind() == reflect.Ptr {
t = t.Elem()
}
if t.Kind() != reflect.Struct {
fmt.Println("参数不是结构体类型")
return
}
// 遍历所有字段
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
fmt.Printf("第%d个字段名称:%sn", i, field.Name)
}
}
根据字段名获取单个字段
如果需要判断结构体是否存在某个字段,或者获取指定字段的信息,可以使用FieldByName(name string)方法,该方法会返回reflect.StructField和一个布尔值,布尔值表示是否找到该字段。
func getFieldByName(u interface{}, fieldName string) {
t := reflect.TypeOf(u)
if t.Kind() == reflect.Ptr {
t = t.Elem()
}
field, ok := t.FieldByName(fieldName)
if !ok {
fmt.Printf("未找到字段:%sn", fieldName)
return
}
fmt.Printf("找到字段%s,类型为:%vn", field.Name, field.Type)
}
获取结构体字段元数据
结构体字段的元数据除了字段名称、类型之外,最常见的就是结构体标签(Tag)。reflect.StructField的Tag属性是reflect.StructTag类型,提供了获取标签值的方法。
读取结构体标签
可以使用Get(key string)方法获取指定key的标签值,如果标签中不存在该key,会返回空字符串。也可以使用Lookup(key string)方法,该方法会返回值和是否存在的布尔值。
func printFieldTags(u interface{}) {
t := reflect.TypeOf(u)
if t.Kind() == reflect.Ptr {
t = t.Elem()
}
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
// 获取json标签
jsonTag := field.Tag.Get("json")
// 获取validate标签
validateTag, ok := field.Tag.Lookup("validate")
fmt.Printf("字段%s:json标签=%s,validate标签存在=%v,值=%sn", field.Name, jsonTag, ok, validateTag)
}
}
常见元数据属性说明
除了Tag之外,reflect.StructField还有其他常用的元数据属性,具体如下表:
| 属性名 | 类型 | 说明 |
|---|---|---|
| Name | string | 字段名称 |
| Type | reflect.Type | 字段的类型信息 |
| Tag | reflect.StructTag | 结构体标签信息 |
| PkgPath | string | 字段的包路径,非导出字段会有值,导出字段为空 |
| Offset | uintptr | 字段在结构体中的偏移量 |
注意事项
- 反射操作只能获取导出字段(首字母大写)的完整信息,非导出字段虽然可以通过
Field()方法获取,但无法修改其值,且PkgPath属性会包含包路径信息。 - 如果传入反射的是结构体值而非指针,只能获取字段信息,无法修改字段值;如果需要修改字段值,需要传入结构体指针,并且通过
Elem()获取可寻址的reflect.Value。 - 调用
FieldByName方法时,如果字段不存在不会panic,而是通过返回的布尔值判断是否找到,使用时需要做好校验。
反射虽然灵活,但会带来一定的性能损耗,且代码可读性不如直接操作类型,因此非必要场景不建议过度使用反射。
完整示例
package main
import (
"fmt"
"reflect"
)
type User struct {
Name string `json:"name" validate:"required"`
Age int `json:"age" validate:"min=1,max=120"`
// 非导出字段
score float64
}
func main() {
u := &User{Name: "张三", Age: 20, score: 95.5}
t := reflect.TypeOf(u)
// 解引用指针
if t.Kind() == reflect.Ptr {
t = t.Elem()
}
fmt.Println("=== 遍历所有字段信息 ===")
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
fmt.Printf("字段名:%s,类型:%v,json标签:%s,导出字段:%vn",
field.Name, field.Type, field.Tag.Get("json"), field.IsExported())
}
fmt.Println("n=== 获取指定字段信息 ===")
field, ok := t.FieldByName("Name")
if ok {
fmt.Printf("字段Name的validate标签:%sn", field.Tag.Get("validate"))
}
}
Go反射结构体字段元数据reflect_StructField修改时间:2026-06-19 15:12:36