如何以一种可扩展的方式遍历 Golang 结构体中的字段,以获取和设置值?

huangapple go评论75阅读模式
英文:

How do you loop through the fields in a Golang struct to get and set values in an extensible way?

问题

我有一个名为Person的结构体。

type Person struct {
    Firstname string
    Lastname  string
    Years     uint8
}

然后我有两个该结构体的实例,PersonA和PersonB。

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

我想编写一个函数,根据每个字段的条件(即非空),将PersonA的值复制到PersonB。我知道如何通过硬编码字段名来实现这一点,但我希望有一个函数,即使我更改了Person结构体,也能正常工作。

我知道Go的反射功能很有帮助,但问题在于获取和设置值需要知道类型,如果要使用类似SetInt的函数。但是有没有一种“简单”的方法来做到这一点?

JavaScript类比
在JavaScript中,你可以使用(for property in someObject)来循环遍历。

for (propt in personA) {
  if (personA[propt] != "") {
    // 做一些操作
    personB[propt] = personA[propt];
  }
}

我排除的选项:

  1. 在每个结构体中跟踪字段,并使用reflect包中的FieldByName和Set*函数的组合。
  2. 手动遍历Person的字段(如下所示)。因为我想对许多其他结构体(如School、Animals等)进行这种类型的“更新”。
if PersonA.Firstname != "" {
  PersonB.Firstname = PersonA.Firstname
}

...

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

下面的问题可以帮助我完成一半的工作,但仍然无法扩展到我想要使用此“更新”函数的所有结构体。

https://stackoverflow.com/questions/6395076/in-golang-using-reflect-how-do-you-set-the-value-of-a-struct-field

其他有用的链接
https://stackoverflow.com/questions/18930910/golang-access-struct-property-by-name

英文:

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.

https://stackoverflow.com/questions/6395076/in-golang-using-reflect-how-do-you-set-the-value-of-a-struct-field

** Other Helpful Links **
https://stackoverflow.com/questions/18930910/golang-access-struct-property-by-name

答案1

得分: 23

使用reflect.ValueOf()将其转换为具体类型。之后,您可以使用reflect.Value.SetString来设置您想要的值。

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")
    }
}
英文:

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: &quot;foo&quot;, Bar: 10}
fields := reflect.TypeOf(structValue)
values := reflect.ValueOf(structValue)

num := fields.NumField()

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

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

答案2

得分: 5

这里的关键是 f2.Set(reflect.Value(f))

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

输出结果:

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

英文:

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

package main

   import (
	&quot;fmt&quot;
	&quot;reflect&quot;
   )

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

	for i := 0; i &lt; s.NumField(); i++ {
		f := s.Field(i)
		f2:= s2.Field(i)
		fmt.Printf(&quot;%d: %s %s = %v\n&quot;, i,
			typeOfT.Field(i).Name, f.Type(), f.Interface())
		fmt.Printf(&quot;%d: %s %s = %v\n&quot;, i,
			typeOfT.Field(i).Name, f2.Type(), f2.Interface())
		f2.Set(reflect.Value(f))
		fmt.Printf(&quot;%d: %s %s = %v\n&quot;, i,
			typeOfT.Field(i).Name, f2.Type(), f2.Interface())
			
	}
	fmt.Println(&quot;t=&quot;,t)
	fmt.Println(&quot;t2=&quot;,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

得分: 2

反射应该是你所需要的。这似乎与“深拷贝”语义相似(尽管不完全相同),在https://godoc.org/github.com/getlantern/deepcopy中已经实现。

你应该能够根据自己的需求进行调整,或者至少从中获取一些想法。

英文:

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.

答案4

得分: 1

你应该使用map[string]interface{},这样会更快(尽管仍然不如使用实际结构体的正确逻辑快)。

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)
}
英文:

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 &quot;fmt&quot;

type Object map[string]interface{}

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

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

答案5

得分: 0

我已经翻译好了你提供的内容:

我甚至不知道这个问题有多少种可能出错的方式...

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)

}

你可以在这里查看完整的代码:链接

英文:

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

package main
import (
&quot;fmt&quot;
&quot;encoding/json&quot;
)
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, &amp;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{
&quot;Mary&quot;,
&quot;Jane&quot;,
26,
}
p2 := Person {
&quot;Random&quot;,
&quot;Lady&quot;,
26,
}
if err := copy(&amp;p1, &amp;p2); err != nil {
fmt.Printf(&quot;ERR: %s\n&quot;, err.Error())
return
}
fmt.Printf(&quot;%v\n&quot;, p2)
}

答案6

得分: 0

由于这个答案包含一个错误并且尚未修复,我将在这里添加它。

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

这里是Go Playground,可以测试一下。

更新:
需要注意的一点是,如果字段名中有下划线,它将被删除。我不确定还有哪些其他注意事项,但是我知道转换不是一对一的。

英文:

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

structValue := FooBar{Foo: &quot;foo&quot;, Bar: 10}
fields := reflect.TypeOf(structValue)
values := reflect.ValueOf(structValue)
num := fields.NumField()
for i := 0; i &lt; num; i++ {
field := fields.Field(i)
value := values.Field(i)
fmt.Print(&quot;Type:&quot;, field.Type, &quot;,&quot;, field.Name, &quot;=&quot;, value, &quot;\n&quot;)
switch field.Type.Kind() {
case reflect.String:
v := value.String()
fmt.Print(v, &quot;\n&quot;)
case reflect.Int:
v := strconv.FormatInt(value.Int(), 10)
fmt.Print(v, &quot;\n&quot;)
case reflect.Int32:
v := strconv.FormatInt(value.Int(), 10)
fmt.Print(v, &quot;\n&quot;)
case reflect.Int64:
v := strconv.FormatInt(value.Int(), 10)
fmt.Print(v, &quot;\n&quot;)
default:
assert.Fail(t, &quot;Not support type of struct&quot;)
}
}

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

huangapple
  • 本文由 发表于 2014年4月29日 04:03:36
  • 转载请务必保留本文链接:https://go.coder-hub.com/23350173.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定