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

Get pointers to all fields of a struct dynamically using reflection

我正在尝试为 golang 构建一个简单的 orm 层。 这将采用一个结构并生成 cols [] 然后可以将其传递给 sql 函数 rows.Scan(cols...) 它采用结构中与它在结果集中找到的每一列相对应的字段指针

这是我的示例结构


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

这是我的通用 ORM 函数

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
}


一旦此方法正常工作,我就可以使用它生成的映射根据我在 rows() 对象中获得的 column_names 创建一个有序的 sql 指针列表 但是我在 .Addr() 方法调用

中遇到以下错误
panic: reflect.Value.Addr of unaddressable value [recovered]
    panic: reflect.Value.Addr of unaddressable value

这不可能吗? 同样在理想情况下,我希望该方法采用接口而不是 *ExampleStruct,以便它可以在不同的数据库模型中重复使用。

错误提示您想要获取其地址的值不可寻址。这是因为即使您将指针传递给 GetSqlColumnToFieldMap(),您也会立即取消引用它并稍后使用 non-pointer 值。

这个值在传递给 reflect.ValueOf() 时被包装在 interface{} 中,并且包装在接口中的值不可寻址。

您不能解引用指针,而是使用 Type.Elem()Value.Elem() 来获取元素类型和指向的值。

像这样:

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
}

通过这个简单的更改就可以了!而且它不依赖于参数类型,您可以将其更改为 interface{} 并传递任何结构指针。

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

正在测试:

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

输出(在 Go Playground 上尝试):

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

注意 Value.Addr() returns the address wrapped in a reflect.Value. To "unwrap" the pointer, use 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
}

这将输出(在 Go Playground 上尝试):

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

有关反射的 in-depth 介绍,请阅读博客 post:The Laws of Reflection