Go语言反射reflect标准库03-通过反射获取类型信息相关操作

10.3通过反射获取类型信息

  1. 在 Go语言中通过调用 reflect.TypeOf 函数,我们可以从一个任何非接口类型的值创建一个 reflect.Type 值。reflect.Type 值表示着此非接口值的类型。通过此值,我们可以得到很多此非接口类型的信息。当然,我们也可以将一个接口值传递给一个 reflect.TypeOf 函数调用,但是此调用将返回一个表示着此接口值的动态类型的 reflect.Type 值。

实际上,reflect.TypeOf 函数的唯一参数的类型为 interface{},reflect.TypeOf 函数将总是返回一个表示着此唯一接口参数值的动态类型的 reflect.Type 值。

那如何得到一个表示着某个接口类型的 reflect.Type 值呢?我们必须通过下面将要介绍的一些间接途径来达到这一目的。

使用reflect.TypeOf()函数可以获得任意值的类型对象(reflect.Type),程序通过类型对象可以访问任意值的类型信息,下面通过例子来获取类型对象的过程:

func main() {
	var a int
	typeOfA := reflect.TypeOf(a)  // 获取a的类型对象
	// 通过类型对象的成员函数,获取到typeOfA变量的类型名为:int, 种类为:int
	fmt.Println(typeOfA.Name(), typeOfA.Kind())
}
  1. 从类型对象中获取类型名称和种类的例子
type Enum int
const (
	Zero Enum = 0
)

func main() {
	type cat struct {}
	// 获取反射类型对象
	typeOfCat := reflect.TypeOf(cat{})
	// 获取类型对象的类型名称和种类名称
	fmt.Println(typeOfCat.Name(), typeOfCat.Kind())  // cat struct

	// 获取反射类型对象
	typeOfZero := reflect.TypeOf(Zero)
	// 获取类型对象的类型名称和种类名称
	fmt.Println(typeOfZero.Name(), typeOfZero.Kind())  // Enum int
}

通过反射获取指针指向的元素类型

go语言程序中对指针获取反射对象时,可以通过reflect.Elem()方法获取这个指针指向的元素类型,这个获取过程被称为取元素,等效于对指针类型变量做了一个*操作,

func main() {
	type cat struct {}
	ins := &cat{}
	// 获取指针类型的反射类型对象
	typeOfIns := reflect.TypeOf(ins)
	// 获取指针类型对象的名称和种类
	fmt.Println(typeOfIns.Name(), typeOfIns.Kind())  // "" Ptr

	// 获取指针指向的元素类型对象
	typeElem := typeOfIns.Elem()
	// 获取元素类型对象的名称和种类
	fmt.Println(typeElem.Name(), typeElem.Kind())  // cat struct
}

通过反射获取结构体的成员类型

任意值通过 reflect.TypeOf() 获得反射对象信息后,如果它的类型是结构体,可以通过反射值对象(reflect.Type)的 NumField() 和 Field() 方法获得结构体成员的详细信息。与成员获取相关的 reflect.Type 的方法如下表所示。

结构体成员访问的方法列表

方法 说明
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) 根据匹配函数匹配需要的字段。当值不是结构体或索引超界时发生宕机
  1. 结构体字段类型
    reflect.Type的Field()方法返回StructField结构,这个结构描述结构体的成员信息,通过这个信息可以获取成员与结构体的关系,如偏移、索引、是否为匿名字段、结构体标签(Struct Tag)等,而且还可以通过 StructField 的 Type 字段进一步获取结构体成员的类型信息。StructField 的结构如下:
type StructField struct {
    Name string          // 字段名
    PkgPath string       // 字段路径
    Type      Type       // 字段反射类型对象
    Tag       StructTag  // 字段的结构体标签
    Offset    uintptr    // 字段在结构体中的相对偏移
    Index     []int      // Type.FieldByIndex中的返回的索引值
    Anonymous bool       // 是否为匿名字段
}

字段说明如下。

Name:为字段名称。
PkgPath:字段在结构体中的路径。
Type:字段本身的反射类型对象,类型为 reflect.Type,可以进一步获取字段的类型信息。
Tag:结构体标签,为结构体字段标签的额外信息,可以单独提取。
Index:FieldByIndex 中的索引顺序。
Anonymous:表示该字段是否为匿名字段。

获取成员反射信息

下面代码中,实例化一个结构体并遍历其结构体成员,在通过reflect.Type的FieldByName()方法查找结构体中指定名称的字段,直接获取其类型信息。
反射访问结构体成员类型及信息:

func main() {
	type cat struct {
		Name string
		Type int `json:"type" id:"100"`
	}
	// 创建cat实例并对两个字段赋值,结构体标签属于类型信息,无需且不能赋值
	ins := cat{"Tom", 1}
	// 获取结构体反射类型对象
	typeOfCat := reflect.TypeOf(ins)
	// 遍历访问结构体成员信息
	for i := 0; i < typeOfCat.NumField(); i++ {
		// 获取成员字段
		// 使用reflect.Type的Field()方法返回的不再是reflect.Type类型,而是StructField结构体
		field := typeOfCat.Field(i)
		// 获取结构体成员名称、类型、标签
		fmt.Printf("name:%s, type:%v, tag:%v\n", field.Name, field.Type, field.Tag)
	}
	/* 输出
	name:Name, type:string, tag:
	name:Type, type:int, tag:json:"type" id:"100"
	 */
	// 通过字段名称获取成员信息
	typeField, ok := typeOfCat.FieldByName("Type")
	if ok {
		// 使用StructField的Tag的Get()方法,根据Tag的名字进行信息获取
		fmt.Printf("name:%s, json:%s, id:%s", typeField.Name, typeField.Tag.Get("json"), typeField.Tag.Get("id"))
	}
	// 输出:name:Type, json:type, id:100
}

go语言结构体标签

  1. 通过 reflect.Type 获取结构体成员信息 reflect.StructField 结构中的 Tag 被称为结构体标签(Struct Tag)。结构体标签是对结构体字段的额外信息标签。
    JSON、BSON 等格式进行序列化及对象关系映射(Object Relational Mapping,简称 ORM)系统都会用到结构体标签,这些系统使用标签设定字段在处理时应该具备的特殊属性和可能发生的行为。这些信息都是静态的,无须实例化结构体,可以通过反射获取到。
  2. 结构体标签的格式
    Tag在结构体字段后方的书写格式如下:key1:"value1" key2:"value2"
    结构体标签由一个或多个键值对组成。键与值使用冒号分隔,值用双引号括起来。键值对之间使用一个空格分隔。
  3. 从结构体标签中获取值
func main() {
	type cat struct {
		Name string
		Type int `json:"type" id:"100"`
	}
	ins := cat{"Tom", 1}
	// 获取结构体的反射类型对象
	typeOfCat := reflect.TypeOf(ins)
	typeField, ok := typeOfCat.FieldByName("Type")
	if ok {
		value, ok := typeField.Tag.Lookup("json")
		if ok {
			fmt.Println("json:", value)
		}else{
			fmt.Println("json标签不存在")
		}
		value, ok = typeField.Tag.Lookup("id")
		if ok {
			fmt.Println("id:", value)
		}else{
			fmt.Println("id标签不存在")
		}
	}
}

输出

json: type
id: 100
  1. 结构体标签格式错误导致的问题
    编写 Tag 时,必须严格遵守键值对的规则。结构体标签的解析代码的容错能力很差,一旦格式写错,编译和运行时都不会提示任何错误,参见下面这个例子:
func main() {
	type cat struct {
		Name string
		Type int `json: "type" id:"100"`
	}
	// 获取结构体反射类型对象
	typeOfCat := reflect.TypeOf(cat{})
	// 通过字段名获取结构体成员信息
	if typeField, ok := typeOfCat.FieldByName("Type"); ok {
		fmt.Println(typeField.Tag.Get("json"))
	}
}

代码输出空字符串,并不会输出期望的type。
第 12 行中,在json:和”type”之间增加了一个空格。这种写法没有遵守结构体标签的规则,因此无法通过 Tag.Get 获取到正确的 json 对应的值。
这个错误在开发中非常容易被疏忽,造成难以察觉的错误。

posted @ 2022-08-16 08:02  专职  阅读(255)  评论(0编辑  收藏  举报