マップを構造体に変換する


94

Goでジェネリックメソッドを作成して、structからの使用データを入力しようとしていますmap[string]interface{}。たとえば、メソッドの署名と使用法は次のようになります。

func FillStruct(data map[string]interface{}, result interface{}) {
    ...
}

type MyStruct struct {
    Name string
    Age  int64
}

myData := make(map[string]interface{})
myData["Name"] = "Tony"
myData["Age"]  = 23

result := &MyStruct{}
FillStruct(myData, result)

// result now has Name set to "Tony" and Age set to 23

これはJSONを仲介として使用して実行できることを知っています。これを行う別のより効率的な方法はありますか?


1
JSONを中間として使用すると、とにかくリフレクションを使用します。stdlib encoding/jsonパッケージを使用して中間ステップを実行することを想定しています。このメソッドを使用できるマップの例と構造体の例を挙げられますか?
Simon Whitehead

そう、それが私がJSONを避けようとしている理由です。うまくいけば、私が知らないより効率的な方法があるようです。
tgrosinger 14年

使用例を挙げていただけますか?のように-このメソッドが何をするかを示すいくつかの疑似コードを示しますか?
Simon Whitehead

うーん... unsafeパッケージに方法があるかもしれませんが、私はそれを試してみません。それ以外..データをそのプロパティに配置するために型に関連付けられたメタデータをクエリできる必要があるため、リフレクションが必要です。これをjson.Marshal+ json.Decode呼び出しでラップするのはかなり簡単ですが、それは反射を2倍にします。
Simon Whitehead

リフレクションに関するコメントを削除しました。私はこれをできるだけ効率的に行うことにもっと興味があります。それが反射を使用することを意味する場合、それは問題ありません。
tgrosinger 14年

回答:


110

最も簡単な方法は、https://github.com/mitchellh/mapstructureを使用することです

import "github.com/mitchellh/mapstructure"

mapstructure.Decode(myData, &result)

自分でやりたい場合は、次のようにします。

http://play.golang.org/p/tN8mxT_V9h

func SetField(obj interface{}, name string, value interface{}) error {
    structValue := reflect.ValueOf(obj).Elem()
    structFieldValue := structValue.FieldByName(name)

    if !structFieldValue.IsValid() {
        return fmt.Errorf("No such field: %s in obj", name)
    }

    if !structFieldValue.CanSet() {
        return fmt.Errorf("Cannot set %s field value", name)
    }

    structFieldType := structFieldValue.Type()
    val := reflect.ValueOf(value)
    if structFieldType != val.Type() {
        return errors.New("Provided value type didn't match obj field type")
    }

    structFieldValue.Set(val)
    return nil
}

type MyStruct struct {
    Name string
    Age  int64
}

func (s *MyStruct) FillStruct(m map[string]interface{}) error {
    for k, v := range m {
        err := SetField(s, k, v)
        if err != nil {
            return err
        }
    }
    return nil
}

func main() {
    myData := make(map[string]interface{})
    myData["Name"] = "Tony"
    myData["Age"] = int64(23)

    result := &MyStruct{}
    err := result.FillStruct(myData)
    if err != nil {
        fmt.Println(err)
    }
    fmt.Println(result)
}

1
ありがとうございました。少し変更したバージョンを使用しています。play.golang.org/p/_JuMm6HMnU
tgrosinger

さまざまな構造体すべてに対してFillStructの動作を望んでおり、すべての構造体に対して定義する必要はありませんfunc (s MyStr...) FillStruct ...。基本構造にFillStructを定義してから、他のすべての構造にその動作を「継承」させることは可能ですか?上記のパラダイムでは、ベース構造体...この場合、 "MyStruct"だけが実際にフィールドを反復させるため、これは不可能です
StartupGuy

:私はあなたが、それはこのような何かを持つ構造体のために働くことができたわけplay.golang.org/p/0weG38IUA9
デイブ・

Mystructにタグを実装することは可能ですか?
vicTROLLA 2016年

1
@abhishek確かに、最初にテキストにマーシャリングし、次にマーシャリングを解除すると、パフォーマンスが低下します。そのアプローチも確かに簡単です。これはトレードオフであり、一般的には、より単純なソリューションを使用します。「仲介者としてJSONを使用してこれを実行できることを知っています。これを実行する別のより効率的な方法はありますか?」という質問のため、この解決策で答えました。このソリューションはより効率的であり、JSONソリューションは一般に実装と推論がより簡単になります。
デイブ

72

Hashicorpのhttps://github.com/mitchellh/mapstructureライブラリは、すぐにこれを実行します。

import "github.com/mitchellh/mapstructure"

mapstructure.Decode(myData, &result)

2番目のresultパラメーターは、構造体のアドレスでなければなりません。


マップキーが存在しuser_name、構造体ファイルが存在する場合はUserNameどうなりますか?
ニコラスジェラ


マップキーが_idで、マップ名がIdの場合、それはデコードされません。
Ravi Shankar、

24
  • それを行う最も簡単な方法は、encoding/jsonパッケージを使用することです

ちょうど例:

package main
import (
    "fmt"
    "encoding/json"
)

type MyAddress struct {
    House string
    School string
}
type Student struct {
    Id int64
    Name string
    Scores float32
    Address MyAddress
    Labels []string
}

func Test() {

    dict := make(map[string]interface{})
    dict["id"] = 201902181425       // int
    dict["name"] = "jackytse"       // string
    dict["scores"] = 123.456        // float
    dict["address"] = map[string]string{"house":"my house", "school":"my school"}   // map
    dict["labels"] = []string{"aries", "warmhearted", "frank"}      // slice

    jsonbody, err := json.Marshal(dict)
    if err != nil {
        // do error check
        fmt.Println(err)
        return
    }

    student := Student{}
    if err := json.Unmarshal(jsonbody, &student); err != nil {
        // do error check
        fmt.Println(err)
        return
    }

    fmt.Printf("%#v\n", student)
}

func main() {
    Test()
}

1
@jackytseに感謝します。これは実際にそれを行うための最良の方法です!! マップ構造は、多くの場合、インターフェース内のネストされたマップでは機能しません。そのため、マップ文字列インターフェースを検討し、それをjsonとして処理することをお勧めします。
Gilles Essoki

ゴー遊び場上記のスニペットのリンク:play.golang.org/p/JaKxETAbsnT
Junaid

13

あなたはそれを行うことができます...それは少し醜いかもしれません、そしてあなたはマッピングタイプに関していくつかの試行錯誤に直面するでしょう..しかし、それの基本的な要点はここにあります:

func FillStruct(data map[string]interface{}, result interface{}) {
    t := reflect.ValueOf(result).Elem()
    for k, v := range data {
        val := t.FieldByName(k)
        val.Set(reflect.ValueOf(v))
    }
}

作業サンプル:http : //play.golang.org/p/PYHz63sbvL


1
これはゼロ値でパニックになるように見えます:reflect: call of reflect.Value.Set on zero Value
James Taylor

@JamesTaylorはい。私の答えは、マッピングするフィールドを正確に知っていることを前提としています。エラー処理(発生しているエラーを含む)の多い同様の回答を求めている場合は、代わりにDavesの回答をお勧めします。
Simon Whitehead

2

私はデイブの答えを採用し、再帰的な機能を追加します。私はまだよりユーザーフレンドリーなバージョンに取り組んでいます。たとえば、マップ内の数値文字列は、構造体でintに変換できる必要があります。

package main

import (
    "fmt"
    "reflect"
)

func SetField(obj interface{}, name string, value interface{}) error {

    structValue := reflect.ValueOf(obj).Elem()
    fieldVal := structValue.FieldByName(name)

    if !fieldVal.IsValid() {
        return fmt.Errorf("No such field: %s in obj", name)
    }

    if !fieldVal.CanSet() {
        return fmt.Errorf("Cannot set %s field value", name)
    }

    val := reflect.ValueOf(value)

    if fieldVal.Type() != val.Type() {

        if m,ok := value.(map[string]interface{}); ok {

            // if field value is struct
            if fieldVal.Kind() == reflect.Struct {
                return FillStruct(m, fieldVal.Addr().Interface())
            }

            // if field value is a pointer to struct
            if fieldVal.Kind()==reflect.Ptr && fieldVal.Type().Elem().Kind() == reflect.Struct {
                if fieldVal.IsNil() {
                    fieldVal.Set(reflect.New(fieldVal.Type().Elem()))
                }
                // fmt.Printf("recursive: %v %v\n", m,fieldVal.Interface())
                return FillStruct(m, fieldVal.Interface())
            }

        }

        return fmt.Errorf("Provided value type didn't match obj field type")
    }

    fieldVal.Set(val)
    return nil

}

func FillStruct(m map[string]interface{}, s interface{}) error {
    for k, v := range m {
        err := SetField(s, k, v)
        if err != nil {
            return err
        }
    }
    return nil
}

type OtherStruct struct {
    Name string
    Age  int64
}


type MyStruct struct {
    Name string
    Age  int64
    OtherStruct *OtherStruct
}



func main() {
    myData := make(map[string]interface{})
    myData["Name"]        = "Tony"
    myData["Age"]         = int64(23)
    OtherStruct := make(map[string]interface{})
    myData["OtherStruct"] = OtherStruct
    OtherStruct["Name"]   = "roxma"
    OtherStruct["Age"]    = int64(23)

    result := &MyStruct{}
    err := FillStruct(myData,result)
    fmt.Println(err)
    fmt.Printf("%v %v\n",result,result.OtherStruct)
}

1

2つのステップがあります。

  1. インターフェースをJSONバイトに変換
  2. JSONバイトを構造体に変換

以下に例を示します。

dbByte, _ := json.Marshal(dbContent)
_ = json.Unmarshal(dbByte, &MyStruct)
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.