uncategorized

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

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

1
2
3
4
type School struct {
ID int `json:"id"`
Name string `json:"name"`
}

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

1
2
3
4
5
6
7
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来简化一下

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

这样做法的问题:Fields的in, out合在一起了,比如在创建School的请求中,ID字段是不能作为入参的,因此这里会有一些不妥。
思考:能不能效仿json tag,自定义一个param tag呢?
解决过程:

  1. 定义基本类型和对外接口

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    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
    }
    }
  2. 实现赋值函数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    //将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
    }
  3. 效果展示

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    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 <nil>}

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