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 ith 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.