通过反射传递嵌套结构的引用

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

Pass by reference nested structures through reflection

问题

以下是翻译好的内容:

type Client struct {
    Id                int
    Age               int
    PrimaryContact    Contact
    Name              string
}

type Contact struct {
    Id        int
    ClientId  int
    IsPrimary bool
    Email     string
}

上面是一段示例代码;我想要实现的目标是:

  • 使用反射循环遍历所有Client结构体字段
  • 对于每个“原始”字段,使用反射设置默认值
  • 对于每个结构体字段,使用递归应用上述步骤

问题在于,当对PrimaryContact字段进行内省并尝试为其任何字段设置值时,会出现以下恐慌错误:

reflect.Value.Set using unaddressable value

如果我没有理解错的话,原因是PrimaryContact是按值传递的,而不是按引用传递,因此当我调用其任何字段的Set方法时,它会更改副本上的字段值,而不是实际参数上的字段值。我该如何解决这个问题?如何使用反射将PrimaryContact字段按引用传递给我的方法?

英文:
type Client struct {
	Id                int
 	Age               int
   	PrimaryContact    Contact
   	Name              string
}
    
type Contact struct {
   	Id        int
   	ClientId  int
   	IsPrimary bool
   	Email     string
}

The above is a sample code; what I am trying to achieve is the following:

  • loop through all Client struct fields using reflection
  • for each "primitive" field set a default value using reflection
  • for each struct field use recursion to apply the above steps

The issue is that when PrimaryContact field is introspected and when I am trying to set a value for any of its fields I end up with the following panic:

> reflect.Value.Set using unaddressable value

The reason if I am not mistaken is that PrimaryContact is passed by value and not by reference so when I would call Set method on any of its fields it would change fields values on the copy and not on the actual argument. How can I overcome this issue? How I could pass PrimaryContact field to my method by reference using reflection?

答案1

得分: 6

我将为您翻译以下内容:

我将其视为一个有趣的练习来进行反射。

但有两个要点:

  • 要设置结构体的字段值,必须将其作为指针传递。
  • 要获取结构体字段的指针值,使用 Value.Addr()

工作解决方案:

package main

import (
	"fmt"
	"reflect"
	"errors"
)

type Client struct {
    Id                int
    Age               int
    PrimaryContact    Contact
    Name              string
}

type Contact struct {
    Id        int
    ClientId  int
    IsPrimary bool
    Email     string
}

func SetDefault(s interface{}) error {
	return setDefaultValue(reflect.ValueOf(s))
}

func setDefaultValue(v reflect.Value) error {
	
	if v.Kind() != reflect.Ptr {
		return errors.New("Not a pointer value")
	}
	
	v = reflect.Indirect(v)
	switch v.Kind() {
		case reflect.Int:
			v.SetInt(42)
		case reflect.String:
			v.SetString("Foo")
		case reflect.Bool:
			v.SetBool(true)
		case reflect.Struct:
			// Iterate over the struct fields
			for i := 0; i < v.NumField(); i++ {
				err := setDefaultValue(v.Field(i).Addr())
				if err != nil {
					return err
				}
			}		
		
		default:
			return errors.New("Unsupported kind: " + v.Kind().String())
	
	}
	
	return nil	
}


func main() {
	a := Client{}
	err := SetDefault(&a)
	if err != nil {
		fmt.Println("Error: ", err)
	} else {
		fmt.Printf("%+v\n", a)
	}
}

输出:

{Id:42 Age:42 PrimaryContact:{Id:42 ClientId:42 IsPrimary:true Email:Foo} Name:Foo}

在线运行: http://play.golang.org/p/-Mpnb7o4vl

英文:

I saw it as a fun exercise to practice reflection.

Two pointers though:

  • In order to set field values of a struct, you must pass it as a pointer
  • To get the pointer value of a struct field, use Value.Addr()

Working solution:

package main
import (
&quot;fmt&quot;
&quot;reflect&quot;
&quot;errors&quot;
)
type Client struct {
Id                int
Age               int
PrimaryContact    Contact
Name              string
}
type Contact struct {
Id        int
ClientId  int
IsPrimary bool
Email     string
}
func SetDefault(s interface{}) error {
return setDefaultValue(reflect.ValueOf(s))
}
func setDefaultValue(v reflect.Value) error {
if v.Kind() != reflect.Ptr {
return errors.New(&quot;Not a pointer value&quot;)
}
v = reflect.Indirect(v)
switch v.Kind() {
case reflect.Int:
v.SetInt(42)
case reflect.String:
v.SetString(&quot;Foo&quot;)
case reflect.Bool:
v.SetBool(true)
case reflect.Struct:
// Iterate over the struct fields
for i := 0; i &lt; v.NumField(); i++ {
err := setDefaultValue(v.Field(i).Addr())
if err != nil {
return err
}
}		
default:
return errors.New(&quot;Unsupported kind: &quot; + v.Kind().String())
}
return nil	
}
func main() {
a := Client{}
err := SetDefault(&amp;a)
if err != nil {
fmt.Println(&quot;Error: &quot;, err)
} else {
fmt.Printf(&quot;%+v\n&quot;, a)
}
}

Output:

{Id:42 Age:42 PrimaryContact:{Id:42 ClientId:42 IsPrimary:true Email:Foo} Name:Foo}

Playground: http://play.golang.org/p/-Mpnb7o4vl

答案2

得分: 0

我认为你只需要使用value.Addr(),它会给你一个对Contact的引用。完整的示例在这里

func main() {
    x := Client{PrimaryContact:Contact{}}
    v := reflect.ValueOf(&x)
    fmt.Println("v type:", v.Type(), ", kind:", v.Kind())
    f := v.Elem().FieldByName("PrimaryContact")
    fmt.Println("f type:", f.Type(), ", kind:", f.Kind())
    p := f.Addr()
    fmt.Println("p type:", p.Type(), ", kind:", p.Kind())
    p.Elem().FieldByName("Id").SetInt(1)
    fmt.Println("Contact Id:", x.PrimaryContact.Id)
}

输出:

v type: *main.Client , kind: ptr
f type: main.Contact , kind: struct
p type: *main.Contact , kind: ptr
Contact Id: 1
英文:

I think you just need to use value.Addr(), which gives you a reference to the Contact. Full example is here.

func main() {
x := Client{PrimaryContact:Contact{}}
v := reflect.ValueOf(&amp;x)
fmt.Println(&quot;v type:&quot;, v.Type(), &quot;, kind:&quot;, v.Kind())
f := v.Elem().FieldByName(&quot;PrimaryContact&quot;)
fmt.Println(&quot;f type:&quot;, f.Type(), &quot;, kind:&quot;, f.Kind())
p := f.Addr()
fmt.Println(&quot;p type:&quot;, p.Type(), &quot;, kind:&quot;, p.Kind())
p.Elem().FieldByName(&quot;Id&quot;).SetInt(1)
fmt.Println(&quot;Contact Id:&quot;, x.PrimaryContact.Id)
}`

Output:

v type: *main.Client , kind: ptr
f type: main.Contact , kind: struct
p type: *main.Contact , kind: ptr
Contact Id: 1

huangapple
  • 本文由 发表于 2014年7月31日 05:36:43
  • 转载请务必保留本文链接:https://go.coder-hub.com/25047424.html
匿名

发表评论

匿名网友

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

确定