英文:
Reading nested structure using reflection
问题
我编写了一个递归函数,用于迭代深层嵌套的结构体,如下所示:
type Container struct {
Name string
Items []Item
}
type Item struct {
Name string
Info Info
Vals []string
}
// 递归读取嵌套的结构体,打印字符串值
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)) // 报错:在结构体值上调用reflect.Value.Elem
case reflect.Slice:
// 如何迭代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++ { // 报错:reflect.Value不支持索引
// 一些操作
}
英文:
I write a recursive function that iterate over deep nested struct like the following:
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))
}
}
how to get access to inner objects (slices, structs) to work with them using reflect?
to iterate over slice i tried to use:
for i:= 0; i < val.Field(i).Slice(0, val.Field(i).Len()); i++ { //error: reflect.Value doesnt support indexing
//some work
}
答案1
得分: 6
你的代码中有几个错误。
首先,只有在传递的值是指针时才需要调用Value.Elem()
。当你遍历字段并找到一个结构类型的字段时,你会递归调用ReadStruct()
,这时它不是一个指针,因此不应该调用Elem()
。
所以你可以这样修改代码:
val := reflect.ValueOf(st)
if val.Kind() == reflect.Ptr {
val = val.Elem()
}
其次,由于你在ReadStruct()
中调用了reflect.ValueOf()
,这意味着你必须将非反射值传递给ReadStruct()
(即不是reflect.Value
类型的值)。
但是当你遍历字段时,调用Value.Field()
,你得到的是包装字段的reflect.Value
。你需要调用Value.Interface()
来提取其中的非反射值,以便在递归调用中传递。
要遍历切片,只需使用Value.Len()
获取切片的长度,使用Value.Index()
获取切片的第i个元素。
以下是修正后的遍历函数版本:
// 我使用了这个类型,因为你在问题中没有发布它。
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.Value
进行操作,这样可以避免这些不必要的调用。
以下是如何实现这一点的代码:
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上尝试这个版本。
英文:
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:
val := reflect.ValueOf(st)
if val.Kind() == reflect.Ptr {
val = val.Elem()
}
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:
// 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())
}
}
}
Testing it:
c := &Container{
Name: "c1",
Items: []Item{
{
Name: "i1",
Info: Info{Key: "k1", Value: "v1"},
},
{
Name: "i2",
Info: Info{Key: "k2", Value: "v2"},
},
},
}
ReadStruct(c)
Output (try it on the Go Playground):
Name=c1
Name=i2
Key=k2
Value=v2
Name=i2
Key=k2
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.Value
s, so you can avoid these unnecessary calls.
This is how you could do that:
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))
}
}
}
This will output the same. Try this one on the Go Playground.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论