英文:
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
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论