go tags

236 阅读9分钟

go-tag

1. 反射reflection

1.1 什么是反射

编译阶段我们并不知道某些变量的具体类型,比如我们的函数里传入了一个interface{} 的接口类型,具体的类型要看传入的变量,也就是运行阶段去判断,这种在运行阶段对变量进行更新判断并能调用变量方法的机制叫反射

1.2 为什么需要反射

比较常见的场景是我们需要判断传入的interface{}的具体数据类型,以便进行不同的数据类型进行不同的操作,这也为通用编程提供了可能性,比如我们可以在拦截层做一些判断,根据变量值使用refelct 判断数据类型,来进行一些操作

1.3 反射的用法

reflect 定义了连个重要的接口type 和value, 可以理解任意借口值在反射中都由reflect.Typereflect.Value两部分组成,并且通过reflect.TypeOfreflect.ValueOf来获取任意对象

下面是Type

// Type is the representation of a Go type.
//
// Not all methods apply to all kinds of types. Restrictions,
// if any, are noted in the documentation for each method.
// Use the Kind method to find out the kind of type before
// calling kind-specific methods. Calling a method
// inappropriate to the kind of type causes a run-time panic.
//
// Type values are comparable, such as with the == operator,
// so they can be used as map keys.
// Two Type values are equal if they represent identical types.
type Type interface {
	// Methods applicable to all types.

	// Align returns the alignment in bytes of a value of
	// this type when allocated in memory.
	Align() int

	// FieldAlign returns the alignment in bytes of a value of
	// this type when used as a field in a struct.
	FieldAlign() int

	// Method returns the i'th method in the type's method set.
	// It panics if i is not in the range [0, NumMethod()).
	//
	// For a non-interface type T or *T, the returned Method's Type and Func
	// fields describe a function whose first argument is the receiver,
	// and only exported methods are accessible.
	//
	// For an interface type, the returned Method's Type field gives the
	// method signature, without a receiver, and the Func field is nil.
	//
	// Methods are sorted in lexicographic order.
	Method(int) Method

	// MethodByName returns the method with that name in the type's
	// method set and a boolean indicating if the method was found.
	//
	// For a non-interface type T or *T, the returned Method's Type and Func
	// fields describe a function whose first argument is the receiver.
	//
	// For an interface type, the returned Method's Type field gives the
	// method signature, without a receiver, and the Func field is nil.
	MethodByName(string) (Method, bool)

	// NumMethod returns the number of methods accessible using Method.
	//
	// Note that NumMethod counts unexported methods only for interface types.
	NumMethod() int

	// Name returns the type's name within its package for a defined type.
	// For other (non-defined) types it returns the empty string.
	Name() string

	// PkgPath returns a defined type's package path, that is, the import path
	// that uniquely identifies the package, such as "encoding/base64".
	// If the type was predeclared (string, error) or not defined (*T, struct{},
	// []int, or A where A is an alias for a non-defined type), the package path
	// will be the empty string.
	PkgPath() string

	// Size returns the number of bytes needed to store
	// a value of the given type; it is analogous to unsafe.Sizeof.
	Size() uintptr

	// String returns a string representation of the type.
	// The string representation may use shortened package names
	// (e.g., base64 instead of "encoding/base64") and is not
	// guaranteed to be unique among types. To test for type identity,
	// compare the Types directly.
	String() string

	// Kind returns the specific kind of this type.
	Kind() Kind

	// Implements reports whether the type implements the interface type u.
	Implements(u Type) bool

	// AssignableTo reports whether a value of the type is assignable to type u.
	AssignableTo(u Type) bool

	// ConvertibleTo reports whether a value of the type is convertible to type u.
	// Even if ConvertibleTo returns true, the conversion may still panic.
	// For example, a slice of type []T is convertible to *[N]T,
	// but the conversion will panic if its length is less than N.
	ConvertibleTo(u Type) bool

	// Comparable reports whether values of this type are comparable.
	// Even if Comparable returns true, the comparison may still panic.
	// For example, values of interface type are comparable,
	// but the comparison will panic if their dynamic type is not comparable.
	Comparable() bool

	// Methods applicable only to some types, depending on Kind.
	// The methods allowed for each kind are:
	//
	//	Int*, Uint*, Float*, Complex*: Bits
	//	Array: Elem, Len
	//	Chan: ChanDir, Elem
	//	Func: In, NumIn, Out, NumOut, IsVariadic.
	//	Map: Key, Elem
	//	Pointer: Elem
	//	Slice: Elem
	//	Struct: Field, FieldByIndex, FieldByName, FieldByNameFunc, NumField

	// Bits returns the size of the type in bits.
	// It panics if the type's Kind is not one of the
	// sized or unsized Int, Uint, Float, or Complex kinds.
	Bits() int

	// ChanDir returns a channel type's direction.
	// It panics if the type's Kind is not Chan.
	ChanDir() ChanDir

	// IsVariadic reports whether a function type's final input parameter
	// is a "..." parameter. If so, t.In(t.NumIn() - 1) returns the parameter's
	// implicit actual type []T.
	//
	// For concreteness, if t represents func(x int, y ... float64), then
	//
	//	t.NumIn() == 2
	//	t.In(0) is the reflect.Type for "int"
	//	t.In(1) is the reflect.Type for "[]float64"
	//	t.IsVariadic() == true
	//
	// IsVariadic panics if the type's Kind is not Func.
	IsVariadic() bool

	// Elem returns a type's element type.
	// It panics if the type's Kind is not Array, Chan, Map, Pointer, or Slice.
	Elem() Type

	// Field returns a struct type's i'th field.
	// It panics if the type's Kind is not Struct.
	// It panics if i is not in the range [0, NumField()).
	Field(i int) StructField

	// FieldByIndex returns the nested field corresponding
	// to the index sequence. It is equivalent to calling Field
	// successively for each index i.
	// It panics if the type's Kind is not Struct.
	FieldByIndex(index []int) StructField

	// FieldByName returns the struct field with the given name
	// and a boolean indicating if the field was found.
	FieldByName(name string) (StructField, bool)

	// FieldByNameFunc returns the struct field with a name
	// that satisfies the match function and a boolean indicating if
	// the field was found.
	//
	// FieldByNameFunc considers the fields in the struct itself
	// and then the fields in any embedded structs, in breadth first order,
	// stopping at the shallowest nesting depth containing one or more
	// fields satisfying the match function. If multiple fields at that depth
	// satisfy the match function, they cancel each other
	// and FieldByNameFunc returns no match.
	// This behavior mirrors Go's handling of name lookup in
	// structs containing embedded fields.
	FieldByNameFunc(match func(string) bool) (StructField, bool)

	// In returns the type of a function type's i'th input parameter.
	// It panics if the type's Kind is not Func.
	// It panics if i is not in the range [0, NumIn()).
	In(i int) Type

	// Key returns a map type's key type.
	// It panics if the type's Kind is not Map.
	Key() Type

	// Len returns an array type's length.
	// It panics if the type's Kind is not Array.
	Len() int

	// NumField returns a struct type's field count.
	// It panics if the type's Kind is not Struct.
	NumField() int

	// NumIn returns a function type's input parameter count.
	// It panics if the type's Kind is not Func.
	NumIn() int

	// NumOut returns a function type's output parameter count.
	// It panics if the type's Kind is not Func.
	NumOut() int

	// Out returns the type of a function type's i'th output parameter.
	// It panics if the type's Kind is not Func.
	// It panics if i is not in the range [0, NumOut()).
	Out(i int) Type

	common() *rtype
	uncommon() *uncommonType
}

type是一个接口类型,通过reflect.TypeOf 我们得到一个Type,这时可以直接调用他所提供的方法来获取信息,下面是一个实例

package main

import (
	"fmt"
	"reflect"
)

func main() {
	a := "hello"
	of := reflect.TypeOf(a)
	v := reflect.ValueOf(a)
	fmt.Println("Name:", of.Name(), "value:", v)
}
//Name: string value: hello

1.4 type和kind

type 是具体的类型比如自定义的struct, kind指的是大类型

type Kind uint
const (
    Invalid Kind = iota  // 非法类型
    Bool                 // 布尔型
    Int                  // 有符号整型
    Int8                 // 有符号8位整型
    Int16                // 有符号16位整型
    Int32                // 有符号32位整型
    Int64                // 有符号64位整型
    Uint                 // 无符号整型
    Uint8                // 无符号8位整型
    Uint16               // 无符号16位整型
    Uint32               // 无符号32位整型
    Uint64               // 无符号64位整型
    Uintptr              // 指针
    Float32              // 单精度浮点数
    Float64              // 双精度浮点数
    Complex64            // 64位复数类型
    Complex128           // 128位复数类型
    Array                // 数组
    Chan                 // 通道
    Func                 // 函数
    Interface            // 接口
    Map                  // 映射
    Ptr                  // 指针
    Slice                // 切片
    String               // 字符串
    Struct               // 结构体
    UnsafePointer        // 底层指针
)

1.5 获取指针里的数据类型

我们使用reflect.TypeOf() 对指针变量进行判断时,得到的是一个指针的类型, 如果我们想得到数据类型,可以使用

reflect.TypeOf(xxx).Elem

1.6 获取结构体的成员类型

通过reflect.TypeOf 得到发射对象的信息后,如果他是结构体,可以通过反射对象的NumField()Field() 获取成员详细信息

方法说明
Field(i int) StructField根据索引返回索引对应的结构体字段的信息,当值不是结构体或索引超界时发生宕机
NumField() int返回结构体成员字段数量,当类型不是结构体或索引超界时发生宕机
FieldByName(name string) (StructField, bool)根据给定字符串返回字符串对应的结构体字段的信息,没有找到时 bool 返回 false,当类型不是结构体或索引超界时发生宕机
FieldByIndex(index []int) StructField多层成员访问时,根据 []int 提供的每个结构体的字段索引,返回字段的信息,没有找到时返回零值。当类型不是结构体或索引超界时发生宕机
FieldByNameFunc(match func(string) bool) (StructField,bool)根据匹配函数匹配需要的字段,当值不是结构体或索引超界时发生宕机

type Person struct {
	Name string `json:"name" id:"1"`
}
//....
man := Person{
  Name: "zhangsan",
}
typeOfMan := reflect.TypeOf(man)

for i := 0; i < typeOfMan.NumField(); i++ {
  fieldType := typeOfMan.Field(i)
  fmt.Printf("name:%v tag:%v", fieldType.Name, fieldType.Tag)
}

通过上面的例子我们发现Field 返回值(StructField)里面包含了字段的信息,下面是StructField 结构

// A StructField describes a single field in a struct.
type StructField struct {
	// Name is the field name.
	Name string

	// PkgPath is the package path that qualifies a lower case (unexported)
	// field name. It is empty for upper case (exported) field names.
	// See https://golang.org/ref/spec#Uniqueness_of_identifiers
	PkgPath string

	Type      Type      // field type
	Tag       StructTag // field tag string
	Offset    uintptr   // offset within struct, in bytes
	Index     []int     // index sequence for Type.FieldByIndex
	Anonymous bool      // is an embedded field
}

1.7 获取结构体的值

type Person struct {
	age  int
	Name string `json:"name" id:"1"`
	Addr string
}
man := Person{
  age:  12,
  Name: "zhangsan",
}
d := reflect.ValueOf(man)
fmt.Println("结构体成员数量:", d.NumField())
fmt.Println("根据名字查找字段", d.FieldByName("age"))
fmt.Println("字段值类型", d.FieldByName("age").Type())

通过上面我可以看出,通过FieldByName 获取的是值,NumField 获取的是结构体的字段数量, ValueOf 里面有很多方法,Type是其中一个获取数据类型的方法

方 法备 注
Field(i int) Value根据索引,返回索引对应的结构体成员字段的反射值对象。当值不是结构体或索引超界时发生宕机
NumField() int返回结构体成员字段数量。当值不是结构体或索引超界时发生宕机
FieldByName(name string) Value根据给定字符串返回字符串对应的结构体字段。没有找到时返回零值,当值不是结构体或索引超界时发生宕机
FieldByIndex(index []int) Value多层成员访问时,根据 []int 提供的每个结构体的字段索引,返回字段的值。 没有找到时返回零值,当值不是结构体或索引超界时发生宕机
FieldByNameFunc(match func(string) bool) Value根据匹配函数匹配需要的字段。找到时返回零值,当值不是结构体或索引超界时发生宕机

2. go tags

关于tags 其实上面的实例已经有提到过,StructField 这个结构体里有个Tag 成员,查看源码我们发现代码如下

type StructTag string

它的下面有两个方法,GetLookup

Get: 根据键获取值,这个实现也是调用了LookUp方式,只是把err屏蔽了

LookUp: 根据键获取值

需要注意的是,定义tag时,一定要注意格式,多一个少一个空格都不行

3. 定义自己的tag

基于上面的案例做一个自己的解析,

type Person struct {
	age  int    `test:"age" default:"18"` // 注意:后面没有空格 
	Name string `json:"name" id:"1"`
	Addr string
}


man := Person{
  age:  12,
  Name: "zhangsan",
}
tagValaue := reflect.TypeOf(man).Field(0).Tag.Get("test")
fmt.Println("tagValue:", tagValaue)//tagValue: age