Reputation: 3291
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?
Upvotes: 4
Views: 3747
Reputation: 78075
I saw it as a fun exercise to practice reflection.
Two pointers though:
Value.Addr()
Working solution:
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)
}
}
Output:
{Id:42 Age:42 PrimaryContact:{Id:42 ClientId:42 IsPrimary:true Email:Foo} Name:Foo}
Playground: http://play.golang.org/p/-Mpnb7o4vl
Upvotes: 6
Reputation: 9110
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(&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)
}`
Output:
v type: *main.Client , kind: ptr
f type: main.Contact , kind: struct
p type: *main.Contact , kind: ptr
Contact Id: 1
Upvotes: 0