简体   繁体   English

使用反射来填充指​​向结构的指针

[英]Using reflection to populate a pointer to a struct

I'd like to iterate over the fields in a struct and prompt for string values to string fields, doing this recursively for fields that are pointers to structs. 我想遍历结构中的字段并提示将字符串值输入到字符串字段,然后对指向结构的字段进行递归操作。

Currently this is what I've tried, but I get an error when trying to set this value in the pointer's string field. 目前这是我尝试过的方法,但是在尝试在指针的字符串字段中设置此值时遇到错误。

package main

import (
    "fmt"
    "reflect"
)

type Table struct {
    PK *Field
}

type Field struct {
    Name string
}


func main() {
    PopulateStruct(&Table{})
}

func PopulateStruct(a interface{}) interface {} {
    typeOf := reflect.TypeOf(a)
    valueOf := reflect.ValueOf(a)
    for i := 0; i < typeOf.Elem().NumField(); i++ {
        switch typeOf.Elem().Field(i).Type.Kind() {
        case reflect.String:
            fmt.Print(typeOf.Elem().Field(i).Name)
            var s string
            fmt.Scanf("%s", &s)
            valueOf.Elem().Field(i).SetString(s)
        case reflect.Ptr:
            ptr := reflect.New(valueOf.Elem().Field(i).Type())
            PopulateStruct(ptr.Elem().Interface())
            valueOf.Elem().Field(i).Set(ptr)
        }
    }
}

Expecting the return value to include an initialised struct with the pointers string field set. 期望返回值包括带有指针字符串字段集的初始化结构。

Getting an error when setting the pointer's string field. 设置指针的字符串字段时发生错误。

panic: reflect: call of reflect.Value.Field on zero Value 恐慌:reflect:在零值上调用reflect.Value.Field

I dropped your code as-is into the Go Playground and it doesn't build because PopulateStruct is declared as returning interface{} but does not actually return anything. 我将您的代码按原样放置到Go Playground中,但是由于PopulateStruct被声明为返回interface{}但实际上并未返回任何内容,所以它没有构建。 Removing the declared return type produces the panic you mention. 删除声明的返回类型会引起您提到的恐慌。

This is because at entry to the outer PopulateStruct call, you have a valid pointer, pointing to a zero-valued Table . 这是因为在外部PopulateStruct调用的入口处,您具有指向零值Table的有效指针。 A zero-valued Table has one element: a nil pointer in it of type *Field . 零值Table具有一个元素:类型为*Field的零指针。 Your loop therefore runs once and finds a reflect.Ptr , as you expected. 因此,循环将运行一次,并按您的期望找到一个reflect.Ptr Adding more diagnostic print messages helps see what's happening: 添加更多诊断打印消息有助于了解发生了什么:

fmt.Printf("PopulateStruct: I have typeOf=%v, valueOf=%v\n", typeOf, valueOf)
for i := 0; i < typeOf.Elem().NumField(); i++ {
    switch typeOf.Elem().Field(i).Type.Kind() {
// ... snipped some code ...
    case reflect.Ptr:
        ptr := reflect.New(valueOf.Elem().Field(i).Type())
        fmt.Println("after allocating ptr, we have:", ptr.Type(), ptr,
            "but its Elem is:", ptr.Elem().Type(), ptr.Elem())

This prints: 打印:

PopulateStruct: I have typeOf=*main.Table, valueOf=&{<nil>}
after allocating ptr, we have: **main.Field 0x40c138 but its Elem is: *main.Field <nil>

Given the way PopulateStruct itself is constructed, we must actually allocate a real Field instance now , before calling PopulateStruct . 给定PopulateStruct本身的构造方式,在调用PopulateStruct之前,我们实际上必须现在分配一个真实的Field实例。 We can do this with: 我们可以这样做:

        p2 := ptr.Elem()
        ptr.Elem().Set(reflect.New(p2.Type().Elem()))

( code borrowed from json.Unmarshal ). json.Unmarshal借来的代码 )。 Now we can fill in this Field , which has one field named Name of type String . 现在我们可以填写该Field ,其中有一个名为Name的字段,类型为String

The overall strategy here is not that great, in my opinion: filling-in probably should take a generic pointer, not specifically a pointer-to- struct pointer. 在我看来,这里的总体策略不是那么好:填充可能应该采用通用指针,而不是专门针对struct指针。 You can then emulate the indirect function in the json unmarshaller. 然后,您可以在json解组器中模拟indirect函数。 However, the addition of these two lines—creating the target object and making the allocated pointer point to it—suffices to make your existing code run. 但是,添加这两行(创建目标对象并使分配的指针指向它)足以使您现有的代码运行。

(Alternatively, you could just create and return a whole instance from scratch, in which case all you need is the type—but I'm assuming you have a pattern in which only some fields are nil.) (或者,您可以只从头创建并返回整个实例,在这种情况下,您只需要类型即可,但我假设您有一个仅将某些字段设为nil的模式。)

Here's the complete Go Playground example. 这是完整的Go Playground示例。 I made a few other changes as there's nothing to scan from when using the playground. 我进行了其他一些更改,因为使用游乐场时无需扫描任何内容。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2025 STACKOOM.COM