用Go开发Server端提供一些JSON数据格式的API,会定义业务Model,同时标记其json名字。1234type School struct { ID int `json:"id"` Name string `json:"name"`}
通常也会复用这个Model来接收创建或者更新请求的参数。1234567var params map[string]interface{}//parse parameters from request url path, queries or body//...s := &School{}s.Name, _ = params["name"].(string)//create school//...
当School的字段很多时,上面的写法会非常烦人,我们可以利用json来简化一下123var s *Schoold, _ := json.Marshal(params)json.Unmarshal(d, &s)
这样做法的问题:Fields的in, out合在一起了,比如在创建School的请求中,ID字段是不能作为入参的,因此这里会有一些不妥。
思考:能不能效仿json tag,自定义一个param tag呢?
解决过程:
定义基本类型和对外接口
12345678910111213141516171819202122232425262728293031323334type 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 := vif 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}}实现赋值函数
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485//将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 := falsefor 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.Namestrs := 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 successdefault:return false}return true}效果展示
1234567891011121314151617181920type 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 *Schoolparams.AssignTo(&s, "param")fmt.Println(s)}//output:{0 Primary School 1 <nil>}
我们发现id字段其实没有赋值,其它字段赋值成功