【问题标题】:Reading nested structure using reflection使用反射读取嵌套结构
【发布时间】:2021-09-18 19:45:21
【问题描述】:

我编写了一个递归函数,它遍历深层嵌套结构,如下所示:

type Container struct {
    Name string
    Items []Item
}

type Item struct {
    Name string
    Info Info
    Vals []string
}
// recursively reads nested struct, prints string values
func ReadStruct(st interface{}) {
    val := reflect.ValueOf(st).Elem()
    for i := 0; i < val.NumField(); i++ {
        fmt.Println(val.Type().Field(i).Type.Kind())
        switch val.Type().Field(i).Type.Kind() {
        case reflect.Struct:
            ReadStruct(val.Field(i)) // panic: call of reflect.Value.Elem on struct Value
        case reflect.Slice:
            // How to iterate over the reflect.Slice? 
        case reflect.String:
            fmt.Printf("%v=%v", val.Type().Field(i).Name, val.Field(i))
        }
    }
  

如何使用反射访问内部对象(切片、结构)以使用它们? 迭代我尝试使用的切片:

for i:= 0; i < val.Field(i).Slice(0, val.Field(i).Len()); i++ { //error: reflect.Value doesnt support indexing
//some work
} 

【问题讨论】:

    标签: go struct reflection slice go-reflect


    【解决方案1】:

    您的代码中有几个错误。

    首先,如果传递的值是一个指针,你只需要调用Value.Elem()。当您遍历字段并找到一个结构类型的字段时,您递归地调用 ReadStruct() 时,这将不是一个指针,因此您不能在上面调用 Elem()

    所以这样做:

    val := reflect.ValueOf(st)
    if val.Kind() == reflect.Ptr {
        val = val.Elem()
    }
    

    接下来,由于您通过调用reflect.ValueOf() 来启动ReadStruct(),因此假设您必须将非反射值传递给ReadStruct()(也就是说,不是reflect.Value 类型的值)。

    但是当您遍历字段时,调用Value.Field(),您会得到一个包裹字段的reflect.Value。您必须调用 Value.Interface() 以从中提取非反射值,以便在递归调用中传递。

    要遍历切片,只需使用Value.Len() 获取切片长度,使用Value.Index() 获取切片的第ith 元素。

    这是您的遍历函数的更正版本:

    // I used this type as you didn't post it in your question.
    type Info struct {
        Key, Value string
    }
    
    func ReadStruct(st interface{}) {
        val := reflect.ValueOf(st)
        if val.Kind() == reflect.Ptr {
            val = val.Elem()
        }
        for i := 0; i < val.NumField(); i++ {
            // fmt.Println(val.Type().Field(i).Type.Kind())
            f := val.Field(i)
            switch f.Kind() {
            case reflect.Struct:
                ReadStruct(f.Interface())
            case reflect.Slice:
                for j := 0; j < f.Len(); j++ {
                    ReadStruct(f.Index(i).Interface())
                }
            case reflect.String:
                fmt.Printf("%v=%v\n", val.Type().Field(i).Name, val.Field(i).Interface())
            }
        }
    }
    

    测试它:

    c := &Container{
        Name: "c1",
        Items: []Item{
            {
                Name: "i1",
                Info: Info{Key: "k1", Value: "v1"},
            },
            {
                Name: "i2",
                Info: Info{Key: "k2", Value: "v2"},
            },
        },
    }
    ReadStruct(c)
    

    输出(在Go Playground 上试试):

    Name=c1
    Name=i2
    Key=k2
    Value=v2
    Name=i2
    Key=k2
    Value=v2
    

    注意:通过使用递归调用,您可以提取和重新获取reflect.Value 值。始终使用reflect.Values 会更有效率,因此您可以避免这些不必要的调用。

    你可以这样做:

    func ReadStruct(st interface{}) {
        readStruct(reflect.ValueOf(st))
    }
    
    func readStruct(val reflect.Value) {
        if val.Kind() == reflect.Ptr {
            val = val.Elem()
        }
        for i := 0; i < val.NumField(); i++ {
            // fmt.Println(val.Type().Field(i).Type.Kind())
            f := val.Field(i)
            switch f.Kind() {
            case reflect.Struct:
                readStruct(f)
            case reflect.Slice:
                for j := 0; j < f.Len(); j++ {
                    readStruct(f.Index(i))
                }
            case reflect.String:
                fmt.Printf("%v=%v\n", val.Type().Field(i).Name, val.Field(i))
            }
        }
    }
    

    这将输出相同的内容。在Go Playground 上试试这个。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-03-19
      • 1970-01-01
      • 1970-01-01
      • 2019-02-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多