我有一个函数可以遍历作为参数传递的接口的所有字段。为了实现这一点,我正在使用反射。问题是我不知道如何获取非指针字段的地址。这是一个例子:
type Z struct {
Id int
}
type V struct {
Id int
F Z
}
type T struct {
Id int
F V
}
上面的代码代表我的测试结构。现在这里是遍历指定结构的实际函数,并列出了有关它的详细信息:
func InspectStruct(o interface{}) {
val := reflect.ValueOf(o)
if val.Kind() == reflect.Interface && !val.IsNil() {
elm := val.Elem()
if elm.Kind() == reflect.Ptr && !elm.IsNil() && elm.Elem().Kind() == reflect.Ptr {
val = elm
}
}
if val.Kind() == reflect.Ptr {
val = val.Elem()
}
for i := 0; i < val.NumField(); i++ {
valueField := val.Field(i)
typeField := val.Type().Field(i)
address := "not-addressable"
if valueField.Kind() == reflect.Interface && !valueField.IsNil() {
elm := valueField.Elem()
if elm.Kind() == reflect.Ptr && !elm.IsNil() && elm.Elem().Kind() == reflect.Ptr {
valueField = elm
}
}
if valueField.Kind() == reflect.Ptr {
valueField = valueField.Elem()
}
if valueField.CanAddr() {
address = fmt.Sprint(valueField.Addr().Pointer())
}
fmt.Printf("Field Name: %s,\t Field Value: %v,\t Address: %v\t, Field type: %v\t, Field kind: %v\n", typeField.Name,
valueField.Interface(), address, typeField.Type, valueField.Kind())
if valueField.Kind() == reflect.Struct {
InspectStruct(valueField.Interface())
}
}
}
这是结构实例化/初始化后的实际测试:
t := new(T)
t.Id = 1
t.F = *new(V)
t.F.Id = 2
t.F.F = *new(Z)
t.F.F.Id = 3
InspectStruct(t)
最后InspectStruct的输出调用:
Field Name: Id, Field Value: 1, Address: 408125440 , Field type: int , Field kind: int
Field Name: F, Field Value: {2 {3}}, Address: 408125444 , Field type: main.V , Field kind: struct
Field Name: Id, Field Value: 2, Address: not-addressable , Field type: int , Field kind: int
Field Name: F, Field Value: {3}, Address: not-addressable , Field type: main.Z , Field kind: struct
Field Name: Id, Field Value: 3, Address: not-addressable , Field type: int , Field kind: int
正如您所看到的,我正在使用递归,因此如果其中一个字段是结构类型,那么我为它调用InspectStruct。 我的问题是,尽管已经为整个结构初始化了所有字段,但是#34; t&#34;层次结构,我无法获得位于比#34; t&#34;更高深度的任何字段的地址。我真的很感激任何帮助。
答案 0 :(得分:12)
传递reflect.Value
代替interface{}
似乎可以解决问题,但我不知道为什么valueField.Interface()
无效。
工作示例:http://play.golang.org/p/nleA2YWMj8
func InspectStructV(val reflect.Value) {
if val.Kind() == reflect.Interface && !val.IsNil() {
elm := val.Elem()
if elm.Kind() == reflect.Ptr && !elm.IsNil() && elm.Elem().Kind() == reflect.Ptr {
val = elm
}
}
if val.Kind() == reflect.Ptr {
val = val.Elem()
}
for i := 0; i < val.NumField(); i++ {
valueField := val.Field(i)
typeField := val.Type().Field(i)
address := "not-addressable"
if valueField.Kind() == reflect.Interface && !valueField.IsNil() {
elm := valueField.Elem()
if elm.Kind() == reflect.Ptr && !elm.IsNil() && elm.Elem().Kind() == reflect.Ptr {
valueField = elm
}
}
if valueField.Kind() == reflect.Ptr {
valueField = valueField.Elem()
}
if valueField.CanAddr() {
address = fmt.Sprintf("0x%X", valueField.Addr().Pointer())
}
fmt.Printf("Field Name: %s,\t Field Value: %v,\t Address: %v\t, Field type: %v\t, Field kind: %v\n", typeField.Name,
valueField.Interface(), address, typeField.Type, valueField.Kind())
if valueField.Kind() == reflect.Struct {
InspectStructV(valueField)
}
}
}
func InspectStruct(v interface{}) {
InspectStructV(reflect.ValueOf(v))
}
答案 1 :(得分:11)
Interface()
不起作用的原因是它返回的接口包装器。为了了解正在发生的事情,让我们看看我们在没有反思的情况下做了什么:
type MyStruct struct {
F Foo
}
type Foo struct {
i int
}
func ExtractField(ptr *MyStruct) interface{} {
return ptr.F
}
func main() {
ms := &MyStruct{Foo{5}}
f := ExtractField(ms).(Foo) // extract value
f.i = 19
fmt.Println(f, ms.F) // ???
fmt.Println(&f == &ms.F) // Not the same!
}
但是,请考虑interface{}
这会返回。什么是包装? ptr.F
的值 - 即副本。这是value.Interface
的作用,它会返回包裹字段的interface{}
。不再有任何指针元数据,它与原始结构完全分离。
正如您所注意到的,passing a value directly至reflect.ValueOf
将始终为CanAddr返回false
作为“顶级” - 因为该地址毫无意义,因为它会为您提供价值副本的地址,改变它并不意味着什么。 (请记住,指针也是值 - 如果你想要像*Foo
这样的指针值字段的地址,你真的在寻找**Foo
)。
所以,在我们上面的示例中,如果我们天真地传入reflect.ValueOf(ExtractField(ms))
,我们会得到ValueOf
f
,这不仅没有您想要的地址 - - 根据反映它甚至不可寻址,因为就反射而言,从不给出有效地址(它可以给你的唯一地址是{内部值副本的地址) {1}} struct)。
那么为什么将Value
传递到兔子洞下面呢?好吧,唯一真实的说法是Value
在您使用reflect.Value
和Elem
时维护必要的元数据,而Field
则不能。因此,虽然interface{}
可能如下所示:
reflect.Value
它可以给你的是这个
// Disclaimer: not the real structure of a reflect.Value
type Value struct {
fieldAddress uintptr
value Foo
}
答案 2 :(得分:2)
我今天去了一个反射兔子洞,从学习这段代码和Jsor的答案中学到了很多,谢谢。我得出了一个不同的结论,虽然你的问题导致了一个更直接的解决方案:
1)当你最初调用函数时,你正在传入指向结构的指针,但是......
2)当您通过调用InspectStruct(valueField.Interface())&#39;进行递归时,不是通过指针传递嵌入式结构,而是按值传递它。
由于您通过了值,go会创建一个临时值,并且不会让您获取该地址。相反,当你递归时,调用valueField.Addr()。Interface(),它将传递指向嵌入式结构的指针。
if valueField.Kind() == reflect.Struct {
- InspectStruct(valueField.Interface())
+ InspectStruct(valueField.Addr().Interface())
}
通过此更改,我得到您期望的输出:
Field Name: Id, Field Value: 1, Address: 842350527552 , Field type: int , Field kind: int
Field Name: F, Field Value: {2 {3}}, Address: 842350527560 , Field type: lib.V , Field kind: struct
Field Name: Id, Field Value: 2, Address: 842350527560 , Field type: int , Field kind: int
Field Name: F, Field Value: {3}, Address: 842350527568 , Field type: lib.Z , Field kind: struct
Field Name: Id, Field Value: 3, Address: 842350527568 , Field type: int , Field kind: int
答案 3 :(得分:1)
@OneofOne的答案是完美的,但最好再添加一个检查
if valueField.IsValid() {
fmt.Printf("Field Name: %s, Field Value: %v, Address: %v, Field type: %v, Field kind: %v\n", typeField.Name,
valueField.Interface(), address, typeField.Type, valueField.Kind())
}
需要它,因为有时您可以从零值结构中请求接口。它会发生,它会恐慌。