使用反射读取嵌套结构

huangapple go评论118阅读模式
英文:

Reading nested structure using reflection

问题

我编写了一个递归函数,用于迭代深层嵌套的结构体,如下所示:

  1. type Container struct {
  2. Name string
  3. Items []Item
  4. }
  5. type Item struct {
  6. Name string
  7. Info Info
  8. Vals []string
  9. }
  10. // 递归读取嵌套的结构体,打印字符串值
  11. func ReadStruct(st interface{}) {
  12. val := reflect.ValueOf(st).Elem()
  13. for i := 0; i < val.NumField(); i++ {
  14. fmt.Println(val.Type().Field(i).Type.Kind())
  15. switch val.Type().Field(i).Type.Kind() {
  16. case reflect.Struct:
  17. ReadStruct(val.Field(i)) // 报错:在结构体值上调用reflect.Value.Elem
  18. case reflect.Slice:
  19. // 如何迭代reflect.Slice?
  20. case reflect.String:
  21. fmt.Printf("%v=%v", val.Type().Field(i).Name, val.Field(i))
  22. }
  23. }
  24. }

如何使用反射访问内部对象(切片、结构体)并对其进行操作?
为了迭代切片,我尝试使用以下代码:

  1. for i := 0; i < val.Field(i).Slice(0, val.Field(i).Len()); i++ { // 报错:reflect.Value不支持索引
  2. // 一些操作
  3. }
英文:

I write a recursive function that iterate over deep nested struct like the following:

  1. type Container struct {
  2. Name string
  3. Items []Item
  4. }
  5. type Item struct {
  6. Name string
  7. Info Info
  8. Vals []string
  9. }
  10. // recursively reads nested struct, prints string values
  11. func ReadStruct(st interface{}) {
  12. val := reflect.ValueOf(st).Elem()
  13. for i := 0; i &lt; val.NumField(); i++ {
  14. fmt.Println(val.Type().Field(i).Type.Kind())
  15. switch val.Type().Field(i).Type.Kind() {
  16. case reflect.Struct:
  17. ReadStruct(val.Field(i)) // panic: call of reflect.Value.Elem on struct Value
  18. case reflect.Slice:
  19. // How to iterate over the reflect.Slice?
  20. case reflect.String:
  21. fmt.Printf(&quot;%v=%v&quot;, val.Type().Field(i).Name, val.Field(i))
  22. }
  23. }

how to get access to inner objects (slices, structs) to work with them using reflect?
to iterate over slice i tried to use:

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

答案1

得分: 6

你的代码中有几个错误。

首先,只有在传递的值是指针时才需要调用Value.Elem()。当你遍历字段并找到一个结构类型的字段时,你会递归调用ReadStruct(),这时它不是一个指针,因此不应该调用Elem()

所以你可以这样修改代码:

  1. val := reflect.ValueOf(st)
  2. if val.Kind() == reflect.Ptr {
  3. val = val.Elem()
  4. }

其次,由于你在ReadStruct()中调用了reflect.ValueOf(),这意味着你必须将非反射值传递给ReadStruct()(即不是reflect.Value类型的值)。

但是当你遍历字段时,调用Value.Field(),你得到的是包装字段的reflect.Value。你需要调用Value.Interface()来提取其中的非反射值,以便在递归调用中传递。

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

以下是修正后的遍历函数版本:

  1. // 我使用了这个类型,因为你在问题中没有发布它。
  2. type Info struct {
  3. Key, Value string
  4. }
  5. func ReadStruct(st interface{}) {
  6. val := reflect.ValueOf(st)
  7. if val.Kind() == reflect.Ptr {
  8. val = val.Elem()
  9. }
  10. for i := 0; i < val.NumField(); i++ {
  11. // fmt.Println(val.Type().Field(i).Type.Kind())
  12. f := val.Field(i)
  13. switch f.Kind() {
  14. case reflect.Struct:
  15. ReadStruct(f.Interface())
  16. case reflect.Slice:
  17. for j := 0; j < f.Len(); j++ {
  18. ReadStruct(f.Index(i).Interface())
  19. }
  20. case reflect.String:
  21. fmt.Printf("%v=%v\n", val.Type().Field(i).Name, val.Field(i).Interface())
  22. }
  23. }
  24. }

测试代码:

  1. c := &Container{
  2. Name: "c1",
  3. Items: []Item{
  4. {
  5. Name: "i1",
  6. Info: Info{Key: "k1", Value: "v1"},
  7. },
  8. {
  9. Name: "i2",
  10. Info: Info{Key: "k2", Value: "v2"},
  11. },
  12. },
  13. }
  14. ReadStruct(c)

输出结果(在Go Playground上尝试):

  1. Name=c1
  2. Name=i2
  3. Key=k2
  4. Value=v2
  5. Name=i2
  6. Key=k2
  7. Value=v2

**注意:**通过使用递归调用,你正在提取和重新获取reflect.Value值。更高效的做法是始终使用reflect.Value进行操作,这样可以避免这些不必要的调用。

以下是如何实现这一点的代码:

  1. func ReadStruct(st interface{}) {
  2. readStruct(reflect.ValueOf(st))
  3. }
  4. func readStruct(val reflect.Value) {
  5. if val.Kind() == reflect.Ptr {
  6. val = val.Elem()
  7. }
  8. for i := 0; i < val.NumField(); i++ {
  9. // fmt.Println(val.Type().Field(i).Type.Kind())
  10. f := val.Field(i)
  11. switch f.Kind() {
  12. case reflect.Struct:
  13. readStruct(f)
  14. case reflect.Slice:
  15. for j := 0; j < f.Len(); j++ {
  16. readStruct(f.Index(i))
  17. }
  18. case reflect.String:
  19. fmt.Printf("%v=%v\n", val.Type().Field(i).Name, val.Field(i))
  20. }
  21. }
  22. }

这将输出相同的结果。在Go Playground上尝试这个版本。

英文:

Couple of errors in your code.

First, you only have to call Value.Elem() if the passed value is a pointer. When you iterate over the fields and you find a field of struct type, and you recursively call ReadStruct() with that, that won't be a pointer and thus you mustn't call Elem() on that.

So do it like this:

  1. val := reflect.ValueOf(st)
  2. if val.Kind() == reflect.Ptr {
  3. val = val.Elem()
  4. }

Next, since you start ReadStruct() by calling reflect.ValueOf(), that assumes you have to pass non-reflect values to ReadStruct() (that is, not values of type reflect.Value).

But when you iterate over the fields, calling Value.Field(), you get a reflect.Value wrapping the field. You have to call Value.Interface() to extract the non-reflect value form it, to be passed in recursive calls.

To iterate over slices, simply use Value.Len() to get the slice length, and Value.Index() to get the i<sup>th</sup> element of the slice.

Here's the corrected version of your traversal function:

  1. // I used this type as you didn&#39;t post it in your question.
  2. type Info struct {
  3. Key, Value string
  4. }
  5. func ReadStruct(st interface{}) {
  6. val := reflect.ValueOf(st)
  7. if val.Kind() == reflect.Ptr {
  8. val = val.Elem()
  9. }
  10. for i := 0; i &lt; val.NumField(); i++ {
  11. // fmt.Println(val.Type().Field(i).Type.Kind())
  12. f := val.Field(i)
  13. switch f.Kind() {
  14. case reflect.Struct:
  15. ReadStruct(f.Interface())
  16. case reflect.Slice:
  17. for j := 0; j &lt; f.Len(); j++ {
  18. ReadStruct(f.Index(i).Interface())
  19. }
  20. case reflect.String:
  21. fmt.Printf(&quot;%v=%v\n&quot;, val.Type().Field(i).Name, val.Field(i).Interface())
  22. }
  23. }
  24. }

Testing it:

  1. c := &amp;Container{
  2. Name: &quot;c1&quot;,
  3. Items: []Item{
  4. {
  5. Name: &quot;i1&quot;,
  6. Info: Info{Key: &quot;k1&quot;, Value: &quot;v1&quot;},
  7. },
  8. {
  9. Name: &quot;i2&quot;,
  10. Info: Info{Key: &quot;k2&quot;, Value: &quot;v2&quot;},
  11. },
  12. },
  13. }
  14. ReadStruct(c)

Output (try it on the Go Playground):

  1. Name=c1
  2. Name=i2
  3. Key=k2
  4. Value=v2
  5. Name=i2
  6. Key=k2
  7. Value=v2

Note: By using recursive calls, you are extracting and re-acquiring reflect.Value values. It would be more efficient to always work with reflect.Values, so you can avoid these unnecessary calls.

This is how you could do that:

  1. func ReadStruct(st interface{}) {
  2. readStruct(reflect.ValueOf(st))
  3. }
  4. func readStruct(val reflect.Value) {
  5. if val.Kind() == reflect.Ptr {
  6. val = val.Elem()
  7. }
  8. for i := 0; i &lt; val.NumField(); i++ {
  9. // fmt.Println(val.Type().Field(i).Type.Kind())
  10. f := val.Field(i)
  11. switch f.Kind() {
  12. case reflect.Struct:
  13. readStruct(f)
  14. case reflect.Slice:
  15. for j := 0; j &lt; f.Len(); j++ {
  16. readStruct(f.Index(i))
  17. }
  18. case reflect.String:
  19. fmt.Printf(&quot;%v=%v\n&quot;, val.Type().Field(i).Name, val.Field(i))
  20. }
  21. }
  22. }

This will output the same. Try this one on the Go Playground.

huangapple
  • 本文由 发表于 2021年7月8日 18:35:03
  • 转载请务必保留本文链接:https://go.coder-hub.com/68299983.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定