go reflect常见应用
Posted 文大侠
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了go reflect常见应用相关的知识,希望对你有一定的参考价值。
目录
借助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常见应用的主要内容,如果未能解决你的问题,请参考以下文章