在golang中,将http表单数据加载到struct中的一般函数

dan*_*nny 7 go

在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


mna*_*mna 8

为了好玩,我试了一下.请注意,我作弊了一点(见评论),但你应该得到照片.使用反射与静态类型分配(如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)


nem*_*emo 7

我建议使用一个特定的接口,而不是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包的工作方式.