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

huangapple go评论90阅读模式
英文:

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()对象中获取的列名创建一个有序的SQL指针列表。然而,我在.Addr()方法调用上遇到以下错误:

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

这是不可能的吗?在理想情况下,我希望该方法接受一个接口而不是*ExampleStruct,以便可以在不同的数据库模型之间重用。

英文:

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:&quot;id&quot;`
	aID    string         `sql:&quot;a_id&quot;`
	UserID    int64          `sql:&quot;user_id&quot;`

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 &lt; ValueOfModel.NumField(); i++ {
		sql_column := typeOfModel.Field(i).Tag.Get(&quot;sql&quot;)
		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.

答案1

得分: 3

错误提示表示您要获取其地址的值是不可寻址的。这是因为尽管您将指针传递给GetSqlColumnToFieldMap(),但您立即对其进行解引用,并在后续的操作中使用非指针值。

当将该值传递给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{}并传递任何结构体指针。

测试代码如下所示:

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()返回包装在reflect.Value中的地址。要“解包”指针,请使用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]

有关反射的详细介绍,请阅读博文:The Laws of Reflection

英文:

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 &lt; v.NumField(); i++ {
		sql_column := t.Field(i).Tag.Get(&quot;sql&quot;)
		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:&quot;id&quot;`
	AID    string `sql:&quot;a_id&quot;`
	UserID int64  `sql:&quot;user_id&quot;`
}

type Point struct {
	X int `sql:&quot;x&quot;`
	Y int `sql:&quot;y&quot;`
}

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

Output (try it on the Go Playground):

map[a_id:&lt;*string Value&gt; id:&lt;*int64 Value&gt; user_id:&lt;*int64 Value&gt;]
map[x:&lt;*int Value&gt; y:&lt;*int Value&gt;]

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 &lt; v.NumField(); i++ {
		colName := t.Field(i).Tag.Get(&quot;sql&quot;)
		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

huangapple
  • 本文由 发表于 2022年3月13日 21:47:17
  • 转载请务必保留本文链接:https://go.coder-hub.com/71457374.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定