Go结构体标签用法

结构体标签根据传统,它是由空格分隔的键值对,每个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函数

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为我们提供了下面方法。

Get方法

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

ValueOf函数

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的反射性能不是很理想,有可能会影响到我们程序的性能,所以应该尝试其他方法。

参考文章

  1. type StructTag

你可能感兴趣的:(reflect,struct,GO学习总结)