Using reflect, how do you set the value of a struct field?
Asked Answered
A

3

143

having a rough time working with struct fields using reflect package. in particular, have not figured out how to set the field value.

type t struct { fi int; fs string }
var r t = t{ 123, "jblow" }
var i64 int64 = 456
  1. getting Name of field i - this seems to work

    var field = reflect.TypeOf(r).Field(i).Name

  2. getting value of field i as a) interface{}, b) int - this seems to work

    var iface interface{} = reflect.ValueOf(r).Field(i).Interface()

    var i int = int(reflect.ValueOf(r).Field(i).Int())

  3. setting value of field i - try one - panic

    reflect.ValueOf(r).Field(i).SetInt( i64 )

    panic: reflect.Value·SetInt using value obtained using unexported field

    assuming it did not like field names "id" and "name", so renamed to "Id" and "Name"

    a) is this assumption correct?

    b) if correct, thought not necessary since in same file / package

  4. setting value of field i - try two (with field names capitalized ) - panic

    reflect.ValueOf(r).Field(i).SetInt( 465 )

    reflect.ValueOf(r).Field(i).SetInt( i64 )

    panic: reflect.Value·SetInt using unaddressable value


Instructions below by @peterSO are thorough and high quality

Four. this works:

reflect.ValueOf(&r).Elem().Field(i).SetInt( i64 )

he documents as well that the field names must be exportable (begin with capital letter)

Airframe answered 18/6, 2011 at 9:24 Comment(2)
the closest example I could find for someone using reflect to set data was comments.gmane.org/gmane.comp.lang.go.general/35045, but even there he used json.Unmarshal to do the actual dirty workAirframe
(the above comment is obsolete)Airframe
T
210

The Go json package marshals and unmarshals JSON from and to Go structures.

Here's a step-by-step example which sets the value of a struct field while carefully avoiding errors.

The Go reflect package has a CanAddr function.

func (v Value) CanAddr() bool

CanAddr returns true if the value's address can be obtained with Addr. Such values are called addressable. A value is addressable if it is an element of a slice, an element of an addressable array, a field of an addressable struct, or the result of dereferencing a pointer. If CanAddr returns false, calling Addr will panic.

The Go reflect package has a CanSet function, which, if true, implies that CanAddr is also true.

func (v Value) CanSet() bool

CanSet returns true if the value of v can be changed. A Value can be changed only if it is addressable and was not obtained by the use of unexported struct fields. If CanSet returns false, calling Set or any type-specific setter (e.g., SetBool, SetInt64) will panic.

We need to make sure we can Set the struct field. For example,

package main

import (
    "fmt"
    "reflect"
)

func main() {
    type t struct {
        N int
    }
    var n = t{42}
    // N at start
    fmt.Println(n.N)
    // pointer to struct - addressable
    ps := reflect.ValueOf(&n)
    // struct
    s := ps.Elem()
    if s.Kind() == reflect.Struct {
        // exported field
        f := s.FieldByName("N")
        if f.IsValid() {
            // A Value can be changed only if it is 
            // addressable and was not obtained by 
            // the use of unexported struct fields.
            if f.CanSet() {
                // change value of N
                if f.Kind() == reflect.Int {
                    x := int64(7)
                    if !f.OverflowInt(x) {
                        f.SetInt(x)
                    }
                }
            }
        }
    }
    // N at end
    fmt.Println(n.N)
}

Output:
42
7

If we can be certain that all the error checks are unnecessary, the example simplifies to,

package main

import (
    "fmt"
    "reflect"
)

func main() {
    type t struct {
        N int
    }
    var n = t{42}
    fmt.Println(n.N)
    reflect.ValueOf(&n).Elem().FieldByName("N").SetInt(7)
    fmt.Println(n.N)
}

BTW, Go is available as open source code. A good way to learn about reflection is to see how the core Go developers use it. For example, the Go fmt and json packages. The package documentation has links to the source code files under the heading Package files.

Tracay answered 18/6, 2011 at 14:29 Comment(8)
Give Up! somewhere in there is an answer, but four hours of work on the json pkg have not yielded it to me. re the reflect pkg, pulling info is pretty straightforward, but setting data requires some of black magic for which I would love to see a simple example somewhere!Airframe
Outstanding! if you're ever in Thailand please let me treat you to a beer or two or three! thank you very muchAirframe
Great practical example, this article completely demystified it for me golang.org/doc/articles/laws_of_reflection.htmlAppend
Awesome example.Here is playground sample of the same code play.golang.org/p/RK8jR_9rPhAparicio
This doesn't set a struct field. This sets a field in a pointer to struct. Obviously, this is not answering the question OP asked.Cardoza
omg, why is go so painful to use. Grumble, grumble grumble. Repeat the above for all the different data types and you see what i meanCropper
Reflect is a terrible thing. Programmer can visit the unexport field just as fmt.Printf can show us the unexport field. Thanks golang team, they won't allow programmers to change the unexport field. Or else something ridiculous will come.Unbrace
Supplement: The necessary condition for CanSet to return true is that the attribute of the struct must have an uppercase prefix.Fatness
G
22

This seems to work:

package main

import (
    "fmt"
    "reflect"
)

type Foo struct {
    Number int
    Text string
}

func main() {
    foo := Foo{123, "Hello"}

    fmt.Println(int(reflect.ValueOf(foo).Field(0).Int()))

    reflect.ValueOf(&foo).Elem().Field(0).SetInt(321)

    fmt.Println(int(reflect.ValueOf(foo).Field(0).Int()))
}

Prints:

123
321
Gosh answered 19/6, 2011 at 13:27 Comment(1)
thanks! now that I've read peterSO's notes, this makes perfect sense. I was using foo, not &foo, so could not be changed, and was unsure what Elem() was about.Airframe
C
2

You can create a helper function to set values:

package main

import (
    "fmt"
    "log"
    "reflect"
    "time"
)

type Apple struct {
    Color     string
    CreatedAt time.Time
}

func SetValue(obj any, field string, value any) {
    ref := reflect.ValueOf(obj)

    // if its a pointer, resolve its value
    if ref.Kind() == reflect.Ptr {
        ref = reflect.Indirect(ref)
    }

    if ref.Kind() == reflect.Interface {
        ref = ref.Elem()
    }

    // should double check we now have a struct (could still be anything)
    if ref.Kind() != reflect.Struct {
        log.Fatal("unexpected type")
    }

    prop := ref.FieldByName(field)
    prop.Set(reflect.ValueOf(value))
}

func main() {
    myStruct := Apple{}
    SetValue(&myStruct, "Color", "red")
    SetValue(&myStruct, "CreatedAt", time.Now())
    fmt.Printf("Result: %+v\n", myStruct)
}

Playground: https://go.dev/play/p/uHoy5n3k1DW

Comforter answered 13/5, 2023 at 18:15 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.