How do you loop through the fields in a Golang str

2019-03-25 05:54发布

问题:

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

回答1:

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")
    }
}


回答2:

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



回答3:

Reflection should be all you need. This seems similar (though not identical) to "deep copy" semantics, which has been implemented at https://godoc.org/code.google.com/p/rog-go/exp/deepcopy

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



回答4:

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)
}


标签: reflection go