简体   繁体   English

使用反射动态获取指向结构的所有字段的指针

[英]Get pointers to all fields of a struct dynamically using reflection

I'm trying to build a simple orm layer for golang.我正在尝试为 golang 构建一个简单的 orm 层。 Which would take a struct and generate the cols [] which can then be passed to sql function rows.Scan(cols...) which takes pointers of fields in the struct corresponding to each of the columns it has found in the result set这将采用一个结构并生成cols []然后可以将其传递给 sql function rows.Scan(cols...)它采用结构中与它在结果集中找到的每一列相对应的字段指针

Here is my example struct这是我的示例结构


type ExampleStruct struct {
    ID        int64          `sql:"id"`
    aID    string         `sql:"a_id"`
    UserID    int64          `sql:"user_id"`

And this is my generic ORM function这是我的通用 ORM function

func GetSqlColumnToFieldMap(model *ExampleStruct) map[string]interface{} {
    
    typeOfModel := reflect.TypeOf(*model)
    ValueOfModel := reflect.ValueOf(*model)
    columnToDataPointerMap := make(map[string]interface{})
    for i := 0; i < ValueOfModel.NumField(); i++ {
        sql_column := typeOfModel.Field(i).Tag.Get("sql")
        structValue := ValueOfModel.Field(i)
        columnToDataPointerMap[sql_column] = structValue.Addr()
    }
    return columnToDataPointerMap
}


Once this method works fine i can use the map it generates to create an ordered list of sql pointers according to the column_names i get in rows() object However i get below error on the .Addr() method call一旦此方法正常工作,我可以使用它生成的 map 根据我在 rows() 中获得的 column_names 创建一个有序的 sql 指针列表 object 但是我在.Addr()方法调用中遇到以下错误

panic: reflect.Value.Addr of unaddressable value [recovered]
    panic: reflect.Value.Addr of unaddressable value

Is it not possible to do this?不可能这样做吗? Also in an ideal scenario i would want the method to take an interface instead of *ExampleStruct so that it can be reused across different db models.同样在理想情况下,我希望该方法采用接口而不是 *ExampleStruct,以便它可以在不同的数据库模型中重用。

The error says the value whose address you want to get is unaddressable.该错误表明您想要获取其地址的值是不可寻址的。 This is because even though you pass a pointer to GetSqlColumnToFieldMap() , you immediately dereference it and work with a non-pointer value later on.这是因为即使您将指针传递给GetSqlColumnToFieldMap() ,您也会立即取消引用它并稍后使用非指针值。

This value is wrapped in an interface{} when passed to reflect.ValueOf() , and values wrappped in interfaces are not addressable.当传递给reflect.ValueOf()时,此值被包装在interface{}中,并且包装在接口中的值不可寻址。

You must not dereference the pointer, but instead use Type.Elem() and Value.Elem() to get the element type and pointed value.您不能取消引用指针,而是使用Type.Elem()Value.Elem()来获取元素类型和指向的值。

Something like this:是这样的:

func GetSqlColumnToFieldMap(model *ExampleStruct) map[string]interface{} {
    t := reflect.TypeOf(model).Elem()
    v := reflect.ValueOf(model).Elem()
    columnToDataPointerMap := make(map[string]interface{})
    for i := 0; i < v.NumField(); i++ {
        sql_column := t.Field(i).Tag.Get("sql")
        structValue := v.Field(i)
        columnToDataPointerMap[sql_column] = structValue.Addr()
    }
    return columnToDataPointerMap
}

With this simple change it works, And it doesn't depend on the parameter type, you may change it to interface{} and pass any struct pointers.通过这个简单的更改它就可以工作,并且它不依赖于参数类型,您可以将其更改为interface{}并传递任何结构指针。

func GetSqlColumnToFieldMap(model interface{}) map[string]interface{} {
    // ...
}

Testing it:测试它:

type ExampleStruct struct {
    ID     int64  `sql:"id"`
    AID    string `sql:"a_id"`
    UserID int64  `sql:"user_id"`
}

type Point struct {
    X int `sql:"x"`
    Y int `sql:"y"`
}

func main() {
    fmt.Println(GetSqlColumnToFieldMap(&ExampleStruct{}))
    fmt.Println(GetSqlColumnToFieldMap(&Point{}))
}

Output (try it on the Go Playground ): Output(在Go 游乐场试试):

map[a_id:<*string Value> id:<*int64 Value> user_id:<*int64 Value>]
map[x:<*int Value> y:<*int Value>]

Note that Value.Addr() returns the address wrapped in a reflect.Value .请注意, Value.Addr()返回包裹在reflect.Value中的地址。 To "unwrap" the pointer, use Value.Interface() :要“展开”指针,请使用Value.Interface()

func GetSqlColumnToFieldMap(model interface{}) map[string]interface{} {
    t := reflect.TypeOf(model).Elem()
    v := reflect.ValueOf(model).Elem()
    m := make(map[string]interface{})
    for i := 0; i < v.NumField(); i++ {
        colName := t.Field(i).Tag.Get("sql")
        field := v.Field(i)
        m[colName] = field.Addr().Interface()
    }
    return m
}

This will output (try it on the Go Playground ):这将是 output(在Go 游乐场上试试):

map[a_id:0xc00007e008 id:0xc00007e000 user_id:0xc00007e018]
map[x:0xc000018060 y:0xc000018068]

For an in-depth introduction to reflection, please read blog post: The Laws of Reflection关于反射的深入介绍,请阅读博文: The Laws of Reflection

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

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