在Golang的开发场景中,结构体tag是附加在结构体字段定义后的元信息,常被用于json序列化、表单校验、数据库ORM映射等场景。当我们需要动态解析结构体的字段属性时,就需要借助reflect反射包来实现对结构体tag的获取和操作。

结构体tag基础定义
结构体tag是在结构体字段声明后,用反引号包裹的字符串,常见的tag格式是key:"value"的形式,多个key之间用空格分隔。比如下面是一个包含常见tag的结构体定义:
package main
import "fmt"
// 定义带tag的结构体
type User struct {
ID int `json:"id" validate:"required" orm:"column:id"`
Name string `json:"name" validate:"required,min=2,max=10" orm:"column:user_name"`
Age int `json:"age" validate:"gte=0,lte=120" orm:"column:user_age"`
}
通过reflect获取结构体tag的步骤
要通过reflect获取结构体tag,需要遵循以下三个核心步骤:
- 第一步:获取结构体的反射类型
reflect.Type,如果是结构体实例,需要先通过reflect.TypeOf()获取类型,注意如果传入的是结构体指针,需要先调用Elem()方法获取指针指向的结构体类型 - 第二步:遍历结构体的所有字段,通过
NumField()方法获取字段总数,再用Field(i)方法获取第i个字段的reflect.StructField信息 - 第三步:从
reflect.StructField的Tag属性中获取需要的tag值,可以通过Get(key)方法获取指定key对应的value,也可以通过Lookup(key)方法同时获取value和是否存在该key的布尔值
完整代码示例
下面的代码实现了遍历User结构体所有字段,并打印每个字段的json、validate、orm三个tag的值:
package main
import (
"fmt"
"reflect"
)
type User struct {
ID int `json:"id" validate:"required" orm:"column:id"`
Name string `json:"name" validate:"required,min=2,max=10" orm:"column:user_name"`
Age int `json:"age" validate:"gte=0,lte=120" orm:"column:user_age"`
}
func main() {
// 创建结构体实例
u := User{
ID: 1,
Name: "张三",
Age: 25,
}
// 获取结构体的反射类型,若是指针需要调用Elem()
t := reflect.TypeOf(u)
// 如果是结构体指针,需要加上下面这行
// t = t.Elem()
// 遍历所有字段
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
fmt.Printf("字段名: %sn", field.Name)
fmt.Printf("字段类型: %vn", field.Type)
// 获取json tag
jsonTag, ok := field.Tag.Lookup("json")
if ok {
fmt.Printf("json tag: %sn", jsonTag)
} else {
fmt.Println("无json tag")
}
// 获取validate tag
validateTag := field.Tag.Get("validate")
fmt.Printf("validate tag: %sn", validateTag)
// 获取orm tag
ormTag := field.Tag.Get("orm")
fmt.Printf("orm tag: %sn", ormTag)
fmt.Println("-------------------")
}
}
运行上述代码,输出结果如下:
字段名: ID 字段类型: int json tag: id validate tag: required orm tag: column:id ------------------- 字段名: Name 字段类型: string json tag: name validate tag: required,min=2,max=10 orm tag: column:user_name ------------------- 字段名: Age 字段类型: int json tag: age validate tag: gte=0,lte=120 orm tag: column:user_age -------------------
操作注意事项
1. 处理指针类型结构体
如果传入reflect.TypeOf()的是结构体指针,直接获取NumField()会报错,需要先调用Elem()方法获取指针指向的实际结构体类型,示例代码如下:
func printStructTagByPtr(ptr interface{}) {
t := reflect.TypeOf(ptr)
// 判断是否为指针类型
if t.Kind() != reflect.Ptr {
fmt.Println("传入的不是指针类型")
return
}
// 获取指针指向的结构体类型
structType := t.Elem()
if structType.Kind() != reflect.Struct {
fmt.Println("指针指向的不是结构体类型")
return
}
// 后续遍历字段逻辑同上
for i := 0; i < structType.NumField(); i++ {
field := structType.Field(i)
fmt.Printf("字段: %s, json tag: %sn", field.Name, field.Tag.Get("json"))
}
}
2. tag的解析规则
结构体tag的解析遵循固定规则,tag字符串中key:"value"的value部分如果包含空格,需要用反引号包裹,否则空格会被识别为不同key的分隔符。另外Get()方法如果找不到对应的key,会返回空字符串,而Lookup()方法可以返回布尔值明确判断key是否存在,建议不确定key是否存在的场景下使用Lookup()。
3. 性能相关说明
反射操作相比直接访问结构体字段会有一定的性能损耗,如果是在高频调用的逻辑中,建议尽量减少反射的使用,或者将反射获取的tag信息缓存起来,避免重复反射解析。
常见应用场景
获取结构体tag的能力在实际开发中有很多应用场景:
- 自定义json序列化规则,比如处理字段命名映射、忽略特定字段
- 实现通用的表单参数校验逻辑,结合validate tag自动校验字段合法性
- ORM框架中自动映射结构体字段和数据库表字段,生成对应的SQL语句
- 实现通用的结构体拷贝、数据转换工具,根据tag规则处理字段映射