在Go中,http表单数据(例如来自POST或PUT请求)可以作为表单的映射进行访问map[string][]string.我很难以一种普遍的方式将其转换为结构体.
例如,我想加载一个地图,如:
m := map[string][]string {
"Age": []string{"20"},
"Name": []string{"John Smith"},
}
Run Code Online (Sandbox Code Playgroud)
进入如下模型:
type Person struct {
Age int
Name string
}
Run Code Online (Sandbox Code Playgroud)
所以我正在尝试编写一个带有签名的函数,该函数LoadModel(obj interface{}, m map[string][]string) []error将表单数据加载到我可以键入强制转换为Person的接口{}中.使用反射,以便我可以在任何字段上使用它,而不仅仅是一个Person,这样我就可以根据需要将字符串从http数据转换为int,boolean等.
在golang中使用这个问题的答案,使用reflect,你如何设置struct字段的值?我可以使用反射设置一个人的价值,例如:
p := Person{25, "John"}
reflect.ValueOf(&p).Elem().Field(1).SetString("Dave")
Run Code Online (Sandbox Code Playgroud)
但是我必须为我拥有的每种类型的结构复制加载函数.当我尝试使用接口{}时它不起作用.
pi := (interface{})(p)
reflect.ValueOf(&pi).Elem().Field(1).SetString("Dave")
// panic: reflect: call of reflect.Value.Field on interface Value
Run Code Online (Sandbox Code Playgroud)
在一般情况下我该怎么做?或者甚至更好,是否有更惯用的Go方式来完成我想要做的事情?
mor*_*aes 10
您需要为一般情况制作开关,并相应地加载不同的字段类型.这是基本部分.
如果在结构中有切片(然后你必须将它们加载到表单字段中的元素数),或者你有嵌套的结构,它会变得更难.
我写了一个这样做的包.请参阅:
http://www.gorillatoolkit.org/pkg/schema
为了好玩,我试了一下.请注意,我作弊了一点(见评论),但你应该得到照片.使用反射与静态类型分配(如nemo的答案)通常需要花费成本,因此请务必在决定中权衡(尽管我没有对其进行基准测试).
此外,明显的免责声明,我没有测试所有边缘情况等,不要只是复制粘贴在生产代码:)
所以这里:
package main
import (
"fmt"
"reflect"
"strconv"
)
type Person struct {
Age int
Name string
Salary float64
}
// I cheated a little bit, made the map's value a string instead of a slice.
// Could've used just the index 0 instead, or fill an array of structs (obj).
// Either way, this shows the reflection steps.
//
// Note: no error returned from this example, I just log to stdout. Could definitely
// return an array of errors, and should catch a panic since this is possible
// with the reflect package.
func LoadModel(obj interface{}, m map[string]string) {
defer func() {
if e := recover(); e != nil {
fmt.Printf("Panic! %v\n", e)
}
}()
val := reflect.ValueOf(obj)
if val.Kind() == reflect.Ptr {
val = val.Elem()
}
// Loop over map, try to match the key to a field
for k, v := range m {
if f := val.FieldByName(k); f.IsValid() {
// Is it assignable?
if f.CanSet() {
// Assign the map's value to this field, converting to the right data type.
switch f.Type().Kind() {
// Only a few kinds, just to show the basic idea...
case reflect.Int:
if i, e := strconv.ParseInt(v, 0, 0); e == nil {
f.SetInt(i)
} else {
fmt.Printf("Could not set int value of %s: %s\n", k, e)
}
case reflect.Float64:
if fl, e := strconv.ParseFloat(v, 0); e == nil {
f.SetFloat(fl)
} else {
fmt.Printf("Could not set float64 value of %s: %s\n", k, e)
}
case reflect.String:
f.SetString(v)
default:
fmt.Printf("Unsupported format %v for field %s\n", f.Type().Kind(), k)
}
} else {
fmt.Printf("Key '%s' cannot be set\n", k)
}
} else {
// Key does not map to a field in obj
fmt.Printf("Key '%s' does not have a corresponding field in obj %+v\n", k, obj)
}
}
}
func main() {
m := map[string]string{
"Age": "36",
"Name": "Johnny",
"Salary": "1400.33",
"Ignored": "True",
}
p := new(Person)
LoadModel(p, m)
fmt.Printf("After LoadModel: Person=%+v\n", p)
}
Run Code Online (Sandbox Code Playgroud)
我建议使用一个特定的接口,而不是interface{}你的LoadModel
,你的类型是为了实现被加载.
例如:
type Loadable interface{
LoadValue(name string, value []string)
}
func LoadModel(loadable Loadable, data map[string][]string) {
for key, value := range data {
loadable.LoadValue(key, value)
}
}
Run Code Online (Sandbox Code Playgroud)
并通过以下方式实现您的Person工具:LoadableLoadModel
type Person struct {
Age int
Name string
}
func (p *Person) LoadValue(name string, value []string) {
switch name {
case "Age":
p.Age, err = strconv.Atoi(value[0])
// etc.
}
}
Run Code Online (Sandbox Code Playgroud)
例如,这是方式,encoding/binary包或encoding/json包的工作方式.