Próbuję utworzyć ogólną metodę w Go, która wypełni struct
dane using z pliku map[string]interface{}
. Na przykład podpis metody i użycie może wyglądać następująco:
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
Wiem, że można to zrobić używając JSON jako pośrednika; czy jest inny skuteczniejszy sposób na zrobienie tego?
encoding/json
pakietu stdlib do wykonania tego kroku pośredniego. Czy możesz podać przykładową mapę i przykładową strukturę, na której ta metoda może być używana?unsafe
paczką… ale nie śmiem tego spróbować. Poza tym .. Refleksja jest wymagana, ponieważ musisz mieć możliwość odpytywania metadanych skojarzonych z typem, aby umieścić dane we właściwościach. Byłoby dość proste zawinięcie tego wjson.Marshal
+json.Decode
wywołania ... ale to jest podwójna refleksja.Odpowiedzi:
Najprostszym sposobem byłoby użycie https://github.com/mitchellh/mapstructure
import "github.com/mitchellh/mapstructure" mapstructure.Decode(myData, &result)
Jeśli chcesz to zrobić sam, możesz zrobić coś takiego:
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) }
źródło
func (s MyStr...) FillStruct ...
dla każdej z nich. Czy można zdefiniować FillStruct dla struktury bazowej, a następnie mieć wszystkie inne struktury „dziedziczą” to zachowanie? W powyższym paradygmacie nie jest to możliwe, ponieważ tylko struktura bazowa ... w tym przypadku "MyStruct" będzie faktycznie miał iterowane polaBiblioteka Hashicorp https://github.com/mitchellh/mapstructure robi to po wyjęciu z pudełka:
import "github.com/mitchellh/mapstructure" mapstructure.Decode(myData, &result)
Drugi
result
parametr musi być adresem struktury.źródło
user_name
a pole struct toUserName
?encoding/json
pakietutylko na przykład:
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() }
źródło
Możesz to zrobić ... może być trochę brzydko i będziesz musiał zmierzyć się z próbami i błędami w zakresie typów mapowania ... ale oto podstawowa istota tego:
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)) } }
Próbka robocza: http://play.golang.org/p/PYHz63sbvL
źródło
reflect: call of reflect.Value.Set on zero Value
Dostosowuję odpowiedź Dave'a i dodaję funkcję rekurencyjną. Nadal pracuję nad bardziej przyjazną dla użytkownika wersją. Na przykład ciąg liczbowy w mapie powinien być w stanie przekonwertować na int w strukturze.
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) }
źródło
Istnieją dwa kroki:
Poniżej przykład:
źródło