go reflect常见应用

Posted 文大侠

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了go reflect常见应用相关的知识,希望对你有一定的参考价值。

目录

1. 不同类型填充

2. 结构体bind


借助golang reflect可以使程序更加灵活和简洁,本文介绍常用的两个场景,抛砖引玉,供参考。

1. 不同类型填充

常见的我们会遇到只关心结构不关心类型的场景,比如常见的函数输入指定的类型来操作。那么能否实现这样的功能,输入不同的结构体,但是它们的字段类型和名称基本一致,使用同一个函数来填充?

如下,使用reflect可实现这一通用fill函数,Person和Age类型不一样,但是通过反射可以做到相同函数填充。

// 万能程序,处理填充结构体字段

type Person struct {
	Name string
	Age  int
}

type Animal struct {
	Name string
	Age  int
}

func fillBySetting(o interface{}, fields map[string]interface{}) error {
	t := reflect.TypeOf(o)

	if t.Kind() != reflect.Ptr || t.Elem().Kind() != reflect.Struct {
		return errors.New("Must input a pointer to struct")
	}

	v := reflect.ValueOf(o)
	for field, value := range fields {

		if ft, ok := v.Elem().Type().FieldByName(field); !ok {
			continue // 对应待设置字段不存在
		} else if ft.Type == reflect.TypeOf(value) { // 类型一致时才设置
			v.Elem().FieldByName(field).Set(reflect.ValueOf(value))
		}

	}

	return nil
}

func TestFill(t *testing.T) {
	p := &Person{}
	a := &Animal{}

	fillBySetting(p, map[string]interface{}{"Name": "wenzhou", "Age": 19})
	fillBySetting(a, map[string]interface{}{"Name": "zhuzhu", "Age": 18})

	t.Log(p)
	t.Log(a)
}

2. 结构体bind

类似json解析,具体流程其实和上述fill一致,

1.通过反射遍历字段

2.按照每个字段tag去取的填充信息中取值

3.校验下取值类型

4.填充

具体代码如下

func TestBind(t *testing.T) {
	value := map[string]string{
		"int_value":    "102",
		"string_value": "old_man",
		"int_array":    "[1,2,3]",
		"string_array": "[\\"1\\",\\"2\\",\\"3\\"]",
	}

	var result BindStruct

	t.Log(result)

	err := BindStructWithMap(value, &result)
	if err != nil {
		t.Log("绑定错误", err)
	}

	t.Log(result)
}

func BindStructWithMap(configMap map[string]string, result interface{}) error {

	BindValue := func(field reflect.Value, tfield reflect.StructField, value string) error {
		switch field.Kind() {
		case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
			res, err := strconv.ParseInt(value, 10, 64)
			if err != nil {
				return err
			}
			field.SetInt(res)

		case reflect.String:
			field.SetString(value)

		case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
			res, err := strconv.ParseUint(value, 10, 64)
			if err != nil {
				return err
			}
			field.SetUint(res)

		case reflect.Float32:
			res, err := strconv.ParseFloat(value, 32)
			if err != nil {
				return err
			}
			field.SetFloat(res)

		case reflect.Float64:
			res, err := strconv.ParseFloat(value, 64)
			if err != nil {
				return err
			}
			field.SetFloat(res)

		case reflect.Slice:
			// 或取数组元素类型和type信息
			elemKind := tfield.Type.Elem().Kind()
			elemType := tfield.Type.Elem()

			fmt.Println(elemKind) // int
			fmt.Println(elemType) // int

			// 对应的配置数组也切分,然后遍历将结果配置数组逐一映射到结果集合
			// strArray--》valArray 一一映射
			value = strings.Trim(strings.Trim(value, "["), "]")
			strArray := strings.Split(value, ",")

			var valArray []reflect.Value
			switch elemKind {
			case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
				for _, e := range strArray {
					ee, err := strconv.ParseInt(e, 10, 64)
					if err != nil {
						return err
					}
					valArray = append(valArray, reflect.ValueOf(ee).Convert(elemType))
				}
			case reflect.String:
				for _, e := range strArray {
					valArray = append(valArray, reflect.ValueOf(strings.Trim(e, "\\"")).Convert(elemType))
				}
			}

			// 重新设置值
			valArr := reflect.Append(field, valArray...)
			field.Set(valArr)
		}

		return nil
	}

	// 被绑定的结构体非指针错误返回
	if reflect.ValueOf(result).Kind() != reflect.Ptr {
		errors.New("input must be a pointer to struct")
	}

	// 被绑定的结构体指针不能为 null
	if reflect.ValueOf(result).IsNil() {
		errors.New("input pointer must not be nil")
	}

	// 获取结果反射信息
	v := reflect.ValueOf(result).Elem()
	t := v.Type()

	// 遍历struct 字段 在已有的map值中查找是否存在,存在则如下赋值
	//v.Field(i).SetInt(res)
	//v.Field(i).SetUint(res)
	//v.Field(i).SetFloat(res)

	for i := 0; i < t.NumField(); i++ {
		tfield := t.Field(i)
		field := v.Field(i)

		tag := tfield.Tag.Get("bingkey")
		fmt.Println(tag)

		// map 中没该变量有则跳过
		value, ok := configMap[tag]
		if !ok {
			continue
		}

		// 绑定上
		err := BindValue(field, tfield, value)
		if nil != err {
			return err
		}

	}

	return nil
}

可以看到,借助reflect,使程序可以简洁很多,但是注意reflect相对性能较低,对于性能较高的场合要不不适用reflect要不第一次取reflect信息后缓存使用。

参考 https://zhuanlan.zhihu.com/p/136371822

 原创,转载请注明来自

以上是关于go reflect常见应用的主要内容,如果未能解决你的问题,请参考以下文章

go reflect常见应用

Android获取各个应用程序的缓存文件代码小片段(使用AIDL)

GoLang反射

Go 每日一库之 reflect

Go 每日一库之 reflect

java中反射(Reflect)的常见应用场景