我想建立一个使用反射设置 struct 字段的程序.我让它适用于顶级字段,但我正在努力处理嵌套的 struct 字段.如何迭代嵌套的 struct 字段?

type Payload struct {
    Type    string   `json:"type"`
    SubItem *SubItem `json:"sub_item"`
}

type SubItem struct {
    Foo string `json:"foo"`
}

func main() {
    var payload Payload
    setValue(&payload, "type", "test1")
    setValue(&payload, "sub_item.foo", "test2")
}

func setValue(structPtr interface{}, key string, value string) {
    structValue := reflect.Indirect(reflect.ValueOf(structPtr))
    for i, subkey := range strings.Split(key, ".") {
        isLast := i == len(strings.Split(key, "."))-1
        var found bool
        // this line is crashing with "reflect: call of reflect.Value.NumField on zero Value"
        for i := 0; i < structValue.NumField(); i++ {
            field := structValue.Type().Field(i)
            jsonTags := strings.Split(field.Tag.Get("json"), ",")
            if jsonTags[0] == subkey {
                found = true
                if isLast {
                    if isLast {
                        // last element
                        // TODO set value
                        fmt.Printf("TODO set value %s to %v", value, structValue)
                        structValue = reflect.Indirect(reflect.ValueOf(structPtr))
                    }
                } else {
                    structValue = reflect.Indirect(reflect.ValueOf(structValue.Field(i).Interface()))
                }
                break
            }
        }
        if !found {
            panic(fmt.Errorf("failed to find field %s", key))
        }
    }
}

推荐答案

使用此函数:

func setValue(p interface{}, key string, value interface{}) {
    v := reflect.ValueOf(p)

    // Loop through the names in key to find the target field.
    for _, name := range strings.Split(key, ".") {

        // If the value is pointer, then
        // - allocate value if ptr is nil.
        // - indirect ptr
        for v.Kind() == reflect.Ptr {
            if v.IsNil() {
                v.Set(reflect.New(v.Type().Elem()))
            }
            v = v.Elem()
        }

        // We expect that the value is struct. Find the 
        // named field.
        v = findJSONField(v, name)
        if !v.IsValid() {
            panic(fmt.Sprintf("could not find field %s", key))
        }
    }
    
    // Set the field.
    v.Set(reflect.ValueOf(value))
}

函数findJSONField通过字段的JSON标记查找 struct 字段:

func findJSONField(v reflect.Value, name string) reflect.Value {
    t := v.Type()
    for i := 0; i < v.NumField(); i++ {
        if tag, _, _ := strings.Cut(t.Field(i).Tag.Get("json"), ","); tag == name {
            return v.Field(i)
        }
    }
    return reflect.Value{}
}

https://go.dev/play/p/DnSuydQgQt9

Go相关问答推荐

如何创建两个连接的io.ReadWriteClosers以用于测试目的

Go协议缓冲区导入问题

ChromeDriver不存在(高朗selenium)

为什么(编码器).EncodeElement忽略";,innerxml";标记?

埃拉托塞尼筛:加快交叉关闭倍数步骤

使用!NOT运算符的Golang文本/模板多个条件

Cypher 查找(多个)最低 node

Go 中的sync.Cond 与 Wait 方法

3 字节切片和有符号整数类型之间的转换

在 Go sync.Map 中,为什么这部分实现不一致或者我误解了什么?

Go struct 匿名字段是公开的还是私有的?

在 Cloud Run 中找不到默认凭据

如何使用 go 包设置标头键和值:shurcooL/graphql 或 hasura/go-graphql-client?

无法使用 gocsv 读取引用字段

为什么当我忽略 template.New() 程序可以成功运行?

Golang 工作池实现意外工作

使用 `didip/tollbooth` 限制每小时最大请求数

如何在 Golang 中使用具有相同名称或特定关键字的行或列重新排列/排序 CSV

使用正则表达式拆分具有相同标题的数据块

从 Go struct 中提取标签作为 reflect.Value