结构体标签根据传统,它是由空格分隔的键值对,每个key不能是空的字符串。由于包含的是键值对,所以一般字符串是以语义字符串的形式出现。例如:
package main
import (
"fmt"
"reflect"
)
func main() {
type S struct {
F string `species:"gopher" color:"blue"`
}
s := S{}
st := reflect.TypeOf(s)
field := st.Field(0)
fmt.Println(field.Tag.Get("color"), field.Tag.Get("species"))
}
TypeOf
函数会返回interface{}
类型变量的动态类型,如果是一个空接口,那么返回值为nil
。
func TypeOf(i interface{}) Type {
//...
}
type Type interface {
// ...
Field(i int) StructField // Field会返回结构体中第i个字段。i的值必须在[0,NumField())范围内。
FieldByName(name string) (StructField, bool) // FieldByName会返回给定名称的结构体字段,bool值用来表示是否查找到
NumField() int // NumField返回结构体字段的数量
}
type StructField struct {
Name string // 字段名称
PkgPath string // PkgPath是限定小写(未导出)字段名称的包路径,对于大写(导出)字段名称,它是空的。上面的示例中由于字段F是导出的,所以PkgPath的值为空。如果将其改为小写,则打印包名
Type Type // 字段类型
Tag StructTag // 字段标签
Offset uintptr // 结构体中的偏移量,以字节为单位
Index []int
Anonymous bool
}
type StructTag string
根据上面这几个类型和函数,我们可以得到含有结构体标签信息的StructTag
。那么如何通过Tag来得到标签内容呢?Go为我们提供了下面方法。
func (tag StructTag) Get(key string) string {
v, _ := tag.Lookup(key)
return v
}
// Lookup方法返回给定key的标签内容,如果存在,会返回字符串。否则会返回一个空的。如果tag没有按传统的键值对格式进行定义,那么Lookup返回的值不确定
func (tag StructTag) Lookup(key string) (value string, ok bool) {
// ...
}
func main() {
type S struct {
F0 string `alias:"field_0"`
F1 string `alias:""`
F2 string
}
s := S{}
st := reflect.TypeOf(s)
for i := 0; i < st.NumField(); i++ {
field := st.Field(i)
if alias, ok := field.Tag.Lookup("alias"); ok {
if alias == "" {
fmt.Println("(blank)")
} else {
fmt.Println(alias)
}
} else {
fmt.Println("(not specified)")
}
}
}
Go web编程时可以利用结构体标签来对参数进行校验,减少每个http请求都要编写参数校验逻辑。例如:
package main
import (
"fmt"
"reflect"
)
type testReflect struct {
F0 string `validate:"required"`
F1 string `validate:"omitempty"`
}
func main() {
params := testReflect{
F0: "hello world",
F1: "hello China",
}
validate(params)
}
// 检查当标签含有validate,且其值为required时,保证对应字段不能为空
func validate(params interface{}) bool {
st := reflect.TypeOf(params)
vv := reflect.ValueOf(params)
for i := 0; i < st.NumField(); i++ {
sd := st.Field(i)
fieldValue := vv.Field(i)
if value, ok := sd.Tag.Lookup("validate"); ok && value == "required" {
// found it
kind := fieldValue.Kind()
switch kind {
case reflect.String:
val := fieldValue.String()
fmt.Println("val:", val)
if val == "" {
return false
} else {
return true
}
case reflect.Int:
val := fieldValue.Int()
fmt.Println("val:", val)
if val == 0 {
return false
} else {
return true
}
// 还可以写其他的类型判断,如果为嵌套类型,还得递归判断
default:
return false
}
}
}
return false
}
// val: hello world
func ValueOf(i interface{}) Value {
// ...
}
// Value结构体上接口有:Cap,Close,Complex,Elem,Field,FieldByIndex,FieldByName,FieldByNameFunc
// Float,Index,Int,CanInterface,Interface,InterfaceData,IsNil,IsValid,Kind,Len,MapIndex,
// MapKeys。下面主要介绍几个上面用到的接口
// Field方法会返回结构体v中的第i个字段信息
func (v Value) Field(i int) Value {
if v.Kind() != Struct {
panic(&ValueError{"reflect.Value.Field", v.kind()})
}
// ...
}
// Kind方法返回v的kind,即字段的类型。上例中我们用了Kind获取了必须参数对应的字段类型
func (v Value) Kind() Kind {
return v.kind()
}
虽然使用反射对结构体进行遍历可以实现参数校验。但由于Go的反射性能不是很理想,有可能会影响到我们程序的性能,所以应该尝试其他方法。