Mihai H
Mihai H

Reputation: 3291

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
}

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

Answers (2)

ANisus
ANisus

Reputation: 78075

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 (
    "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

laher
laher

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

Related Questions