如何按顺序将数组中的值分配给结构体字段?

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

How to assign to struct fields from an array of values in order?

问题

我知道你可以使用字面量创建一个结构体,按顺序列出字段:

type Foo struct {
    A string 
    B string
    C string 
}

foo := Foo{"foo", "bar", "baz"}

是否有办法以动态方式做同样的事情?我有一个值的数组(实际上是一个数组的数组),我想按字段顺序将它们分配给一个结构体数组,并且字段数量比三个要多得多。有没有一种方法可以说“按顺序从这个值的数组中分配给这个结构体的字段”?我真的不想写一堆 structArray[i].field1 = dataArray[i][0]; structArray[i].field2 = dataArray[i][1] 等等。

我目前的想法是使用 reflect,但这似乎有点过度复杂,或者创建一个字段名称的数组,构建 JSON 字符串并进行解析。有更好的想法吗?

英文:

I know you can create a struct with a literal, listing the fields in order:

type Foo struct {
    A string 
    B string
    C string 
}

foo := Foo{ "foo", "bar", "baz" }

Is there any way to do the same thing dynamically? I have an array of values (actually an array of arrays) and I want to assign them to an array of structs in field order, and there are rather more than three fields. Is there a way to say "assign this struct's fields from this array of values in order"? I really don't want to write a bunch of structArray[i].field1 = dataArray[i][0]; structArray[i].field2 = dataArray[i][1], etc.

My thoughts so far have been to use reflect, which seems overkillish, or maybe to create an array of field names and build json strings and unmarshal them. Any better ideas?

答案1

得分: 4

使用反射,你可以编写如下的函数:

func populate(dst interface{}, src interface{}) {
	v := reflect.ValueOf(dst)
	if v.Type().Kind() != reflect.Ptr {
		panic("dst must be a pointer")
	}
	v = v.Elem()
    if v.Type().Kind() != reflect.Struct {
        panic("dst must be a pointer to struct")
    } 

	w := reflect.ValueOf(src)
	if w.Type().Kind() != reflect.Slice {
		panic("src must be a slice")
	}
	for i := 0; i < v.NumField(); i++ {
        // 如果需要支持任意类型的源切片
 		value := w.Index(i)
		if value.Type().Kind() == reflect.Interface {
			value = value.Elem()
		}
		v.Field(i).Set(value)
	}
}

你必须确保dst是可寻址的,因此需要将Foo的指针传递给populate函数;并且源切片中的第i个元素实际上可以赋值给结构体中对应的第i个字段。

上述代码是一个简化版本。如果你认为调用者可能会出错,你可以添加额外的检查,例如使用CanAddrAssignableTo

调用示例:

func main() {
	f := Foo{}
	populate(&f, []string{"foo", "bar", "baz"})
	fmt.Println(f) // {foo bar baz}
}

这里还有一个示例,展示了如何将[]any类型的切片作为源切片传递,以处理结构体字段不全是相同类型的情况:https://go.dev/play/p/G8qjDCt79C7

英文:

With reflection you can write a function like this:

func populate(dst any, src any) {
	v := reflect.ValueOf(dst)
	if v.Type().Kind() != reflect.Pointer {
		panic(&quot;dst must be a pointer&quot;)
	}
	v = v.Elem()
    if v.Type().Kind() != reflect.Struct {
        panic(&quot;dst must be a pointer to struct&quot;)
    } 

	w := reflect.ValueOf(src)
	if w.Type().Kind() != reflect.Slice {
		panic(&quot;src must be a slice&quot;)
	}
	for i := 0; i &lt; v.NumField(); i++ {
        // in case you need to support source slices of arbitrary types
 		value := w.Index(i)
		if value.Type().Kind() == reflect.Interface {
			value = value.Elem()
		}
		v.Field(i).Set(value)
	}
}

You must make sure that dst is addressable, hence pass a pointer to Foo into populate; and that the i-th element in the source slice is actually assignable to the corresponding i-th field in the struct.

The code above is in a quite simplified form. You can add additional checks to it, e.g. with CanAddr or AssignableTo, if you think callers may misbehave.

Call it like:

func main() {
	f := Foo{}
	populate(&amp;f, []string{&quot;foo&quot;, &quot;bar&quot;, &quot;baz&quot;})
	fmt.Println(f) // {foo bar baz}
}

Here's a playground that also shows that you can pass a slice of []any as the source slice, in case the struct fields aren't all the same type: https://go.dev/play/p/G8qjDCt79C7

huangapple
  • 本文由 发表于 2022年11月7日 23:43:05
  • 转载请务必保留本文链接:https://go.coder-hub.com/74349201.html
匿名

发表评论

匿名网友

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

确定