Golang-基于reflect和tag自动填充struct数据


原文链接: Golang-基于reflect和tag自动填充struct数据

用Go开发Server端提供一些JSON数据格式的API,会定义业务Model,同时标记其json名字。

type School struct {
ID int json:"id"
Name string json:"name"
}

通常也会复用这个Model来接收创建或者更新请求的参数。

var params map[string]interface{}
//parse parameters from request url path, queries or body
//...
s := &School{}
s.Name, _ = params["name"].(string)
//create school
//...

当School的字段很多时,上面的写法会非常烦人,我们可以利用json来简化一下

var s *School
d, _ := json.Marshal(params)
json.Unmarshal(d, &s)

这样做法的问题:Fields的in, out合在一起了,比如在创建School的请求中,ID字段是不能作为入参的,因此这里会有一些不妥。

思考:能不能效仿json tag,自定义一个param tag呢?

解决过程:

定义基本类型和对外接口

type M map[string]interface{}

// 将m中的值赋给ptr指向的struct的相应字段
func (m M) AssignTo(ptr interface{}, tagName string) bool {
v := reflect.ValueOf(ptr)
if v.IsValid() == false {

  panic("not valid")

}

//找到最后指向的值,或者空指针,空指针是需要进行初始化的
for v.Kind() == reflect.Ptr && !v.IsNil() {

  v = v.Elem()

}

tv := v
if tv.Kind() == reflect.Ptr && tv.CanSet() {

  //对空指针进行初始化,暂时用临时变量保存
  tv.Set(reflect.New(tv.Type().Elem()))
  tv = tv.Elem()

}

if tv.Kind() != reflect.Struct {

  panic("not struct")

}

if assign(tv, m, tagName) { //赋值成功,将临时变量赋给原值

  if v.Kind() == reflect.Ptr {
     v.Set(tv.Addr())
  } else {
     v.Set(tv)
  }
  return true

} else {

  return false

}
}

  1. 实现赋值函数

//将src中的值填充到dstValue中
func assign(dstVal reflect.Value, src interface{}, tagName string) bool {
sv := reflect.ValueOf(src)
if !dstVal.IsValid() || !sv.IsValid() {

  return false

}

if dstVal.Kind() == reflect.Ptr {

  //初始化空指针
  if dstVal.IsNil() && dstVal.CanSet() {
     dstVal.Set(reflect.New(dstVal.Type().Elem()))
  }
  dstVal = dstVal.Elem()

}

// 判断可否赋值,小写字母开头的字段、常量等不可赋值
if !dstVal.CanSet() {

  return false

}

switch dstVal.Kind() {
case reflect.Bool: //TODO...
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: //TODO...
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: //TODO...
case reflect.String: //TODO...
case reflect.Slice://TODO...
case reflect.Map://TODO...
case reflect.Struct:

  if sv.Kind() != reflect.Map || sv.Type().Key().Kind() != reflect.String {
     return false
  }

  success := false
  for i := 0; i < dstVal.NumField(); i++ {
     fv := dstVal.Field(i)
     if fv.IsValid() == false || fv.CanSet() == false {
        continue
     }

     ft := dstVal.Type().Field(i)
     name := ft.Name
     strs := strings.Split(ft.Tag.Get(tagName), ",")
     if strs[0] == "-" { //处理ignore的标志
        continue
     }

     if len(strs[0]) > 0 {
        name = strs[0]
     }

     fsv := sv.MapIndex(reflect.ValueOf(name))
     if fsv.IsValid() {
        if fv.Kind() == reflect.Ptr && fv.IsNil() {
           pv := reflect.New(fv.Type().Elem())
           if assign(pv, fsv.Interface(), tagName) {
              fv.Set(pv)
              success = true
           }
        } else {
           if assign(fv, fsv.Interface(), tagName) {
              success = true
           }
        }
     } else if ft.Anonymous {
        //尝试对匿名字段进行递归赋值,跟JSON的处理原则保持一致
        if fv.Kind() == reflect.Ptr && fv.IsNil() {
           pv := reflect.New(fv.Type().Elem())
           if assign(pv, src, tagName) {
              fv.Set(pv)
              success = true
           }
        } else {
           if assign(fv, src, tagName) {
              success = true
           }
        }
     }
  }
  return success

default:

  return false

}

return true
}

  1. 效果展示

type Room struct {
ID int param:"-" json:"id"
Name string param:"name" json:"name"
}

type School struct {
ID int param:"-" json:"id"
Name string param:"name" json:"name"
RoomID int param:"room_id" json:"-"
Room *Room param:"-" json:"room"
}

func main() {
params := M{"id": "123", "name": "Primary School", "room_id": "1"}
var s *School
params.AssignTo(&s, "param")
fmt.Println(s)
}

//output:
{0 Primary School 1 }

我们发现id字段其实没有赋值,其它字段赋值成功

`