6

Goでは、httpフォームデータ(POSTまたはPUTリクエストなど)にフォームのマップとしてアクセスできますmap[string][]string。これを一般化可能な方法で構造体に変換するのに苦労しています。

たとえば、次のようなマップをロードしたいとします。

m := map[string][]string {
    "Age": []string{"20"},
    "Name": []string{"John Smith"},
}

次のようなモデルに:

type Person struct {
    Age   int
    Name string
}

そのため、フォームデータをインターフェイス{}にロードする署名を使用して関数を作成しようとしていLoadModel(obj interface{}, m map[string][]string) []errorます。このインターフェイスは、Personにキャストバックして入力できます。リフレクションを使用して、Personだけでなく、任意のフィールドを持つ任意の構造体タイプで使用できるようにし、必要に応じて文字列をhttpデータからint、booleanなどに変換できるようにします。

golangでこの質問への回答を使用し、reflectを使用して、構造体フィールドの値をどのように設定しますか?リフレクトを使用して人の価値を設定できます。例:

p := Person{25, "John"}
reflect.ValueOf(&p).Elem().Field(1).SetString("Dave")

しかし、その後、私が持っているすべてのタイプの構造体のロード関数をコピーする必要があります。インターフェイス{}で試してみると、機能しません。

pi := (interface{})(p)
reflect.ValueOf(&pi).Elem().Field(1).SetString("Dave")
// panic: reflect: call of reflect.Value.Field on interface Value

一般的なケースでこれを行うにはどうすればよいですか?またはさらに良いことに、私がやろうとしていることを達成するためのより慣用的なGoの方法はありますか?

4

3 に答える 3

10

一般的なケースではスイッチを作成し、それに応じてさまざまなフィールドタイプをロードする必要があります。これは基本的な部分です。

構造体にスライスがある場合(フォームフィールドの要素数までそれらをロードする必要がある場合)、または構造体をネストしている場合は、さらに難しくなります。

私はこれを行うパッケージを作成しました。参照してください:

http://www.gorillatoolkit.org/pkg/schema

于 2012-10-17T09:05:28.503 に答える
9

楽しみのために、私はそれを試してみました。私は少しだましましたが(コメントを参照)、画像を取得する必要があることに注意してください。通常、リフレクションと静的に型付けされた割り当て(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)
}
于 2012-10-16T02:16:23.613 に答える
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)
    }
}

そして、次のようにPerson実装Loadableすることで実装しますLoadModel

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. 
    }
}

これは、たとえば、encoding/binaryパッケージまたはパッケージが機能する方法です。encoding/json

于 2012-10-16T00:18:06.357 に答える