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?
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)
}
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.
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
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()
}
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)
}