web-dev-qa-db-fra.com

Conversion de map en struct

J'essaie de créer une méthode générique dans Go qui remplira une struct en utilisant les données d'un map[string]interface{}. Par exemple, la signature et l’utilisation de la méthode peuvent ressembler à ceci:

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

Je sais que cela peut être fait en utilisant JSON comme intermédiaire; Y a-t-il un autre moyen plus efficace de le faire?

58
tgrosinger

Le moyen le plus simple serait d’utiliser https://github.com/mitchellh/mapstructure

import "github.com/mitchellh/mapstructure"

mapstructure.Decode(myData, &result)

Si vous voulez le faire vous-même, vous pouvez faire quelque chose comme ceci:

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)
}
58
dave

La bibliothèque https://github.com/mitchellh/mapstructure de Hashicorp effectue cette opération:

import "github.com/mitchellh/mapstructure"

mapstructure.Decode(myData, &result)

Le deuxième paramètre result doit être une adresse de la structure.

53
yunspace

Vous pouvez le faire ... cela peut devenir un peu moche et vous serez confronté à quelques essais et erreurs en termes de types de mappage ... mais voici l'essentiel de son contenu:

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

Exemple de travail: http://play.golang.org/p/PYHz63sbvL

12
Simon Whitehead

juste par exemple

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

func Test() {

    dict := make(map[string]interface{})
    dict["id"] = 201902181425
    dict["name"] = "jackytse"
    dict["scores"] = 123.456
    dict["address"] = map[string]string{"home":"address1", "company":"address2"}
    dict["labels"] = []string{"aries", "warmhearted", "frank"}

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

    type AddressDefine struct {
        Home string
        Company string
    }
    type Student struct {
        Id int64
        Name string
        Scores float32
        Address AddressDefine
        Labels []string
    }
    people := Student{}
    if err := json.Unmarshal(jsonbody, &people); err != nil {
        // do error check
        fmt.Println(err)
    }

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

func main() {
   Test()
}
0
jackytse

J'adapte la réponse de Dave et ajoute une fonctionnalité récursive. Je travaille toujours sur une version plus conviviale. Par exemple, une chaîne numérique de la carte devrait pouvoir être convertie en int dans la structure.

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)
}
0
rox