英文:
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个字段。
上述代码是一个简化版本。如果你认为调用者可能会出错,你可以添加额外的检查,例如使用CanAddr或AssignableTo。
调用示例:
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("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++ {
// 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(&f, []string{"foo", "bar", "baz"})
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
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。


评论