简体   繁体   中英

Get pointers to all fields of a struct dynamically using reflection

I'm trying to build a simple orm layer for golang. 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

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

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

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.

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.

This value is wrapped in an interface{} when passed to reflect.ValueOf() , and values wrappped in interfaces are not addressable.

You must not dereference the pointer, but instead use Type.Elem() and Value.Elem() to get the element type and pointed value.

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.

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

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

This will output (try it on the Go Playground ):

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 technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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