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

时间:2019-08-12 17:50:58

标签: go reflection

我想遍历struct中的字段并提示输入字符串值来表示string字段,然后对指向struct的字段进行递归操作。

目前这是我尝试过的方法,但是尝试在指针的字符串字段中设置此值时出现错误。

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)
        }
    }
}

期望返回值包含带有指针字符串字段集的初始化结构。

设置指针的字符串字段时出错。

恐慌:反射:在零值上调用reflect.Value.Field

1 个答案:

答案 0 :(得分:1)

我将您的代码原样放置到Go Playground中,但由于PopulateStruct被声明为返回interface{},但实际上未返回任何内容,所以它无法构建。删除声明的返回类型会引起您提到的恐慌。

这是因为在进入外部PopulateStruct调用时,您具有指向零值Table的有效指针。零值Table有一个元素:类型为*Field的零指针。因此,您的循环将运行一次并找到一个reflect.Ptr,正如您所期望的那样。添加更多诊断打印消息有助于了解发生了什么:

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())

此打印:

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

鉴于PopulateStruct本身的构造方式,我们实际上必须在调用Field之前分配一个真实的PopulateStruct实例 now 。我们可以这样:

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

code borrowed from json.Unmarshal)。现在我们可以填写此Field,其中有一个名为Name,类型为String的字段。

在我看来,这里的总体策略不是那么好:填充可能应该采用通用指针,而不是专门指向struct的指针。然后,您可以在json解组器中模拟indirect函数。但是,添加这两行(创建目标对象并使分配的指针指向该对象)足以使您的现有代码运行。

(或者,您可以从头开始创建并返回整个实例,在这种情况下,您所需要的只是类型-但我假设您有一个仅包含 some 字段的模式没有。)

Here's the complete Go Playground example.我做了一些其他更改,因为使用游乐场时无需扫描任何内容。