如何使用反射动态构造结构切片
How to construct slice of structs dynamically using reflection
我试图用指针构造 Book 结构的切片,但我无法在 Go 中使用反射来实现它。
[]*Book 结构指针的 Book 切片,请注意 scanResults 方法可能接收任何类型的切片而不仅仅是 Book 结构。所以我希望在运行时动态构建一个切片
你能告诉我我在下面的代码片段中犯了什么错误吗?
package main
import (
"reflect"
"errors"
"fmt"
)
type Book struct {
Id int
Title string
Price float32
}
func main() {
var dest []*Book
scanResults(&dest)
}
func scanResults(dest interface{}) error{
resultsFromExternalSource := []interface{}{10 , "user-name" , float32(22)}
value := reflect.ValueOf(dest)
if value.Kind() != reflect.Ptr {
return errors.New("must pass a pointer, not a value, to scan results into struct destination")
}
sliceElement := reflect.TypeOf(dest).Elem()
if sliceElement.Kind() != reflect.Slice {
return fmt.Errorf("expected %s but got %s", reflect.Slice, sliceElement.Kind())
}
structPtr := sliceElement.Elem()
if structPtr.Kind() != reflect.Ptr {
return fmt.Errorf("expected %s but got %s", reflect.Ptr, structPtr.Kind())
}
structElemType := reflect.TypeOf(structPtr).Elem()
if structElemType.Kind() != reflect.Struct {
return fmt.Errorf("expected %s but got %s", reflect.Struct, structElemType.Kind())
}
structRecordInterface := reflect.New(structElemType).Elem().Interface() // create a new struct
structRecordType := reflect.TypeOf(structRecordInterface)
structRecordValue := reflect.ValueOf(structRecordType)
for i, result := range resultsFromExternalSource {
if structRecordValue.Field(i).CanSet() {
structRecordValue.Field(i).Set(reflect.ValueOf(result))
} else {
varName := structRecordType.Field(i).Name
varType := structRecordType.Field(i).Type
return fmt.Errorf("cannot scan results into passed struct destination as the struct field %v with %v type is not settable", varName, varType)
}
}
return nil
}
你快到了。这是一些带有注释的工作代码:
var errBadArg = errors.New("must pass pointer to slice of pointer to struct")
func scanResults(dest interface{}) error {
resultsFromExternalSource := [][]interface{}{
{10, "user-name", float32(22)},
{20, "i-love-reflect", float32(100)},
}
// Get reflect.Value for the destination confirm that
// the destination is a pointer to a slice of pointers
// to a struct. The tests can be omitted if it's acceptable
// to panic on bad input argument.
destv := reflect.ValueOf(dest)
if destv.Kind() != reflect.Ptr {
return errBadArg
}
// Deference the pointer to get the slice.
destv = destv.Elem()
if destv.Kind() != reflect.Slice {
return errBadArg
}
elemt := destv.Type().Elem()
if elemt.Kind() != reflect.Ptr {
return errBadArg
}
// "deference" the element type to get the struct type.
elemt = elemt.Elem()
if elemt.Kind() != reflect.Struct {
return errBadArg
}
// For each row in the result set...
for j, row := range resultsFromExternalSource {
// Return error if more columns than fields in struct.
if len(row) > elemt.NumField() {
return errors.New("result larger than struct")
}
// Allocate a new slice element.
elemp := reflect.New(elemt)
// Dereference the pointer for field access.
elemv := elemp.Elem()
for i, col := range row {
fieldv := elemv.Field(i)
colv := reflect.ValueOf(col)
// Check to see if assignment to field will work
if !colv.Type().AssignableTo(fieldv.Type()) {
return fmt.Errorf("cannot assign %s to %s in row %d column %d", colv.Type(), fieldv.Type(), j, i)
}
// Set the field.
fieldv.Set(colv)
}
// Append element to the slice.
destv.Set(reflect.Append(destv, elemp))
}
return nil
}
我试图用指针构造 Book 结构的切片,但我无法在 Go 中使用反射来实现它。
[]*Book 结构指针的 Book 切片,请注意 scanResults 方法可能接收任何类型的切片而不仅仅是 Book 结构。所以我希望在运行时动态构建一个切片
你能告诉我我在下面的代码片段中犯了什么错误吗?
package main
import (
"reflect"
"errors"
"fmt"
)
type Book struct {
Id int
Title string
Price float32
}
func main() {
var dest []*Book
scanResults(&dest)
}
func scanResults(dest interface{}) error{
resultsFromExternalSource := []interface{}{10 , "user-name" , float32(22)}
value := reflect.ValueOf(dest)
if value.Kind() != reflect.Ptr {
return errors.New("must pass a pointer, not a value, to scan results into struct destination")
}
sliceElement := reflect.TypeOf(dest).Elem()
if sliceElement.Kind() != reflect.Slice {
return fmt.Errorf("expected %s but got %s", reflect.Slice, sliceElement.Kind())
}
structPtr := sliceElement.Elem()
if structPtr.Kind() != reflect.Ptr {
return fmt.Errorf("expected %s but got %s", reflect.Ptr, structPtr.Kind())
}
structElemType := reflect.TypeOf(structPtr).Elem()
if structElemType.Kind() != reflect.Struct {
return fmt.Errorf("expected %s but got %s", reflect.Struct, structElemType.Kind())
}
structRecordInterface := reflect.New(structElemType).Elem().Interface() // create a new struct
structRecordType := reflect.TypeOf(structRecordInterface)
structRecordValue := reflect.ValueOf(structRecordType)
for i, result := range resultsFromExternalSource {
if structRecordValue.Field(i).CanSet() {
structRecordValue.Field(i).Set(reflect.ValueOf(result))
} else {
varName := structRecordType.Field(i).Name
varType := structRecordType.Field(i).Type
return fmt.Errorf("cannot scan results into passed struct destination as the struct field %v with %v type is not settable", varName, varType)
}
}
return nil
}
你快到了。这是一些带有注释的工作代码:
var errBadArg = errors.New("must pass pointer to slice of pointer to struct")
func scanResults(dest interface{}) error {
resultsFromExternalSource := [][]interface{}{
{10, "user-name", float32(22)},
{20, "i-love-reflect", float32(100)},
}
// Get reflect.Value for the destination confirm that
// the destination is a pointer to a slice of pointers
// to a struct. The tests can be omitted if it's acceptable
// to panic on bad input argument.
destv := reflect.ValueOf(dest)
if destv.Kind() != reflect.Ptr {
return errBadArg
}
// Deference the pointer to get the slice.
destv = destv.Elem()
if destv.Kind() != reflect.Slice {
return errBadArg
}
elemt := destv.Type().Elem()
if elemt.Kind() != reflect.Ptr {
return errBadArg
}
// "deference" the element type to get the struct type.
elemt = elemt.Elem()
if elemt.Kind() != reflect.Struct {
return errBadArg
}
// For each row in the result set...
for j, row := range resultsFromExternalSource {
// Return error if more columns than fields in struct.
if len(row) > elemt.NumField() {
return errors.New("result larger than struct")
}
// Allocate a new slice element.
elemp := reflect.New(elemt)
// Dereference the pointer for field access.
elemv := elemp.Elem()
for i, col := range row {
fieldv := elemv.Field(i)
colv := reflect.ValueOf(col)
// Check to see if assignment to field will work
if !colv.Type().AssignableTo(fieldv.Type()) {
return fmt.Errorf("cannot assign %s to %s in row %d column %d", colv.Type(), fieldv.Type(), j, i)
}
// Set the field.
fieldv.Set(colv)
}
// Append element to the slice.
destv.Set(reflect.Append(destv, elemp))
}
return nil
}