26

I have a struct Person.

type Person struct {
    Firstname string       
    Lastname  string       
    Years     uint8       
}

Then I have two instances of this struct, PersonA and PersonB.

PersonA := {"", "Obama", 6}
PersonB := {"President", "Carter", 8}

I want to write a function that copies the values from PersonA to PersonB given some condition for each field (i.e. non-empty). I know how to do this by hard-coding the field names, but I want a function that works even if I change the Person struct.

I know Go reflections is helpful, but the issue is getting and setting the values requires knowing the types, if you want to use something like SetInt. But is there is a "simple" way to do this?

** Javascript analogy ** In Javascript, you could just do a (for property in someObject) to loop through.

(for propt in personA) {
  if personA[propt] != "" {
    // do something
    personB[propt] = personA[propt]
  }
}

Options I've ruled out:

  1. Keeping track of the fields in each struct in a map, then using a combination of FieldByName and the collection of Set* functions in the reflect pkg.

  2. Creating a loop through the fields of Person manually (below). Because I want to do this type of "update" for many other structs (School, Animals, etc...)

    if PersonA.Firstname != "" {
      PersonB.Firstname = PersonA.Firstname 
    }
    

    ...

    if PersonA.Years != "" {
      PersonB.Years = PersonA.Years 
    }
    

The question below gets me half-way there, but still isn't extensible to all structs for which I want to utilize this "update" function.

in golang, using reflect, how do you set the value of a struct field?

** Other Helpful Links ** GoLang: Access struct property by name

0

6 Answers 6

22

Use reflect.ValueOf() to convert to concrete type. After that you could use reflect.Value.SetString to set the value you want.

structValue := FooBar{Foo: "foo", Bar: 10}
fields := reflect.TypeOf(structValue)
values := reflect.ValueOf(structValue)

num := fields.NumField()

for i := 0; i < num; i++ {
    field := fields.Field(i)
    value := values.Field(i)
    fmt.Print("Type:", field.Type, ",", field.Name, "=", value, "\n")

    switch value.Kind() {
    case reflect.String:
        v := value.String()
        fmt.Print(v, "\n")
    case reflect.Int:
        v := strconv.FormatInt(value.Int(), 10)
        fmt.Print(v, "\n")
    case reflect.Int32:
        v := strconv.FormatInt(value.Int(), 10)
        fmt.Print(v, "\n")
    case reflect.Int64:
        v := strconv.FormatInt(value.Int(), 10)
        fmt.Print(v, "\n")
    default:
        assert.Fail(t, "Not support type of struct")
    }
}
Sign up to request clarification or add additional context in comments.

2 Comments

Sorry but this isn't working! go.dev/play/p/3-Swft2YjIK ; see stackoverflow.com/questions/67236727/… for more
I fixed it! go.dev/play/p/kaac0W7Cnyf The issue is that value.Kind() should be field.Type.Kind()
5

Here is the solution f2.Set(reflect.Value(f)) is the key here

package main

   import (
    "fmt"
    "reflect"
   )

   func main() {
    type T struct {
        A int
        B string
    }
    t := T{23, "skidoo"}
    t2:= T{}
    s := reflect.ValueOf(&t).Elem()
    s2 := reflect.ValueOf(&t2).Elem()
    typeOfT := s.Type()
    fmt.Println("t=",t)
    fmt.Println("t2=",t2)

    for i := 0; i < s.NumField(); i++ {
        f := s.Field(i)
        f2:= s2.Field(i)
        fmt.Printf("%d: %s %s = %v\n", i,
            typeOfT.Field(i).Name, f.Type(), f.Interface())
        fmt.Printf("%d: %s %s = %v\n", i,
            typeOfT.Field(i).Name, f2.Type(), f2.Interface())
        f2.Set(reflect.Value(f))
        fmt.Printf("%d: %s %s = %v\n", i,
            typeOfT.Field(i).Name, f2.Type(), f2.Interface())

    }
    fmt.Println("t=",t)
    fmt.Println("t2=",t2)
}

Output:

t= {23 skidoo}
t2= {0 }
0: A int = 23
0: A int = 0
0: A int = 23
1: B string = skidoo
1: B string = 
1: B string = skidoo
t= {23 skidoo}
t2= {23 skidoo}

http://play.golang.org/p/UKFMBxfbZD

Comments

2

Reflection should be all you need. This seems similar (though not identical) to "deep copy" semantics, which has been implemented at https://godoc.org/github.com/getlantern/deepcopy

You should be able to adapt that to your needs, or at least take some ideas from it.

2 Comments

There's a huge cost, in performance and code complexity, to using reflection. @platwp, might be worth posting a higher-level question about what you're trying to build when this comes up. If JavaScript-ish flexibility is that important, you might even want to use dynamic collections (map[string]interface{} or whatever) instead of object types.
@twotwotwo I was hoping to avoid the "complexity" part. If there's no "easy" way to do it, then dynamic collections might make sense. Thanks!
1

You should use a map[string]interface{} instead, gonna be much faster (although still not as fast as you used the proper logic with actual structs).

package main

import "fmt"

type Object map[string]interface{}

var m = Object{
    "Firstname": "name",
    "Lastname":  "",
    "years":     uint8(10),
}

func main() {
    var cp = Object{}
    for k, v := range m {
        if s, ok := v.(string); ok && s != "" {
            cp[k] = s
        } else if ui, ok := v.(uint8); ok {
            cp[k] = ui
        }
    }
    fmt.Printf("%#v\n", cp)
}

Comments

0

I don't even know how many ways this can go wrong...

package main

import (
    "fmt"
    "encoding/json"
)

type Serializable interface {
    fromMap(map[string]interface{}) error
    toMap() (map[string]interface{}, error)
}

type Person struct {
    Firstname string       
    Lastname  string       
    Years     uint8       
}

func (p *Person) fromMap(m map[string]interface{}) error {
    b, err := json.Marshal(m)
    if err != nil {
        return err
    }

    if err := json.Unmarshal(b, p); err != nil {
        return err
    }
    return nil
}

func (p Person) toMap() (map[string]interface{}, error) {
    b, err := json.Marshal(p)
    if err != nil {
        return nil, err
    }
    m := map[string]interface{}{}
    if err := json.Unmarshal(b, &m); err != nil {
        return nil, err
    }
    return m, nil
}

func copy(p1 Serializable, p2 Serializable) error {

    m1, err := p1.toMap()
    if err != nil {
        return err
    }

    m2, err := p2.toMap()
    if err != nil {
        return err
    }

    for k := range m1 {
        m2[k] = m1[k]
    }

    if err := p2.fromMap(m2); err != nil {
        return err
    }
    return nil
}

func main() {
    p1 := Person{
        "Mary",
        "Jane",
        26,
    }

    p2 := Person {
        "Random",
        "Lady",
        26,
    }

    if err := copy(&p1, &p2); err != nil {
        fmt.Printf("ERR: %s\n", err.Error())
        return
    }

    fmt.Printf("%v\n", p2)

}

Comments

0

Since this answer contains a bug and hasn't been fixed yet I'll add it here

structValue := FooBar{Foo: "foo", Bar: 10}
fields := reflect.TypeOf(structValue)
values := reflect.ValueOf(structValue)

num := fields.NumField()

for i := 0; i < num; i++ {
    field := fields.Field(i)
    value := values.Field(i)
    fmt.Print("Type:", field.Type, ",", field.Name, "=", value, "\n")

    switch field.Type.Kind() {
    case reflect.String:
        v := value.String()
        fmt.Print(v, "\n")
    case reflect.Int:
        v := strconv.FormatInt(value.Int(), 10)
        fmt.Print(v, "\n")
    case reflect.Int32:
        v := strconv.FormatInt(value.Int(), 10)
        fmt.Print(v, "\n")
    case reflect.Int64:
        v := strconv.FormatInt(value.Int(), 10)
        fmt.Print(v, "\n")
    default:
        assert.Fail(t, "Not support type of struct")
    }
}

Here the Go Playground to see test it out

UPDATE: One thing to note is that if your field name has an underscore in it it will be deleted. I'm not sure what other gotchas exist but do know that the conversion is not 1:1

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.