Golang-通过反射将map数组转为结构体数组

做这个的需求是这样的,我在写一个数据组件,用来统一多个类型数据库的组件,简单的说就是通过tcp请求传入一个SQL语句,就会返回类似于mysql的相应结果,同时可以确保MySQL,Redis的数据一致性,初衷是为了减少MySQL的查询次数。

而最初的查询返回统一是一个map数组,也就是[]map[string]interface{},但有一个问题就是,客户端拿到数据后,需要对类型进行断言,这里参考了orm的源码,以及网上的一些文章,然后用反射完成了单个结构体赋值,也就是orm中的err := o.Read(&user),做数组时参考了orm中的func (o *rawSet) QueryRows(containers ...interface{}) (int64, error)

遇到的问题:

  • CanSet()方法返回false:解决方法就是传地址,这里所有的Can类型检测方法就是针对当前的reflect.Value进行校验,如果是一个地址类的value,那就可以进行相应的修改。

  • 结构体赋值问题:要想访问结构体中的字段,有一个大前提条件就是首字母大写,而正常情况下MySQL存储的字段是下划线连接的小写字母,通过json包转换为golang的字段就变成了驼峰,但首字母是小写,这时候就要进行首字母处理。

  • 数组增加元素:首先是要通过反射获取到数组元素的类型,然后通过类型去new一个结构体的实例出来,这里用到了reflect.New()方法,同时在最后的时候,要进行深度拷贝才可以将数组完全赋值给传进来的地址上。

代码如下:

package main

import (
"errors"
"fmt"
"reflect"
"strings"
)

func main() {
mList := []map[string]interface{}{
{"Id": 213, "Name": "zhaoliu", "Sex": "男"},
{"Id": 56, "Name": "zhangsan", "Sex": "男"},
{"Id": 7, "Name": "lisi", "Sex": "女"},
{"Id": 978, "Name": "wangwu", "Sex": "男"},
}

type User struct {
Id int
Name string
Sex string
}
users := []*User{}

mapToStruct(mList, &users)
fmt.Printf("users: %+v\n", users)
}

func mapToStruct(mList []map[string]interface{}, model interface{}) (err error) {
val := reflect.Indirect(reflect.ValueOf(model))
typ := val.Type()

fmt.Printf("val: %v\n", val)
fmt.Printf("typ: %v\n", typ)

for _, r := range mList {
mVal := reflect.Indirect(reflect.New(typ.Elem().Elem())).Addr()
//fmt.Printf("mVal: %+v\n", mVal)
for key, val := range r {
err = setField(mVal.Interface(), key, val)
if err != nil {
return err
}
}
//fmt.Printf("mVal: %+v\n", mVal)
val = reflect.Append(val, mVal)
}
fmt.Printf("val: %+v\n", val.Interface())
//model = val.Interface()
return err
}

//用map的值替换结构的值
func setField(obj interface{}, name string, value interface{}) error {
// 将首字母转换为大写
sl := strings.Split(name, "")
sl[0] = strings.ToUpper(sl[0])
name = strings.Join(sl, "")

structValue := reflect.ValueOf(obj).Elem() //结构体属性值
//fmt.Printf("structValue: %+v\n", structValue)
structFieldValue := structValue.FieldByName(name) //结构体单个属性值
//fmt.Printf("structFieldValue: %+v\n", structFieldValue)
if !structFieldValue.IsValid() {
return fmt.Errorf("No such field: %s in obj", name)
}

if !structFieldValue.CanSet() {
return fmt.Errorf("Cannot set %s field value", name)
}

structFieldType := structFieldValue.Type() //结构体的类型
val := reflect.ValueOf(value) //map值的反射值

if structFieldType != val.Type() {
return errors.New("type is err")
}

structFieldValue.Set(val)
return nil
}

END