Я пытаюсь создать в 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 в качестве посредника; есть ли другой более эффективный способ сделать это?
encoding/json
пакет stdlib для выполнения этого промежуточного шага ... Не могли бы вы привести пример карты и пример структуры, на которой можно было бы использовать этот метод?unsafe
пакетом есть способ ... но я не осмеливаюсь его пробовать. Помимо этого ... Требуется отражение, так как вам нужно иметь возможность запрашивать метаданные, связанные с типом, чтобы поместить данные в его свойства. Было бы довольно просто обернуть это в вызовыjson.Marshal
+json.Decode
... но это вдвое больше, чем отражение.Ответы:
Самый простой способ - использовать 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) }
источник
func (s MyStr...) FillStruct ...
для каждой. Можно ли определить FillStruct для базовой структуры, чтобы все остальные мои структуры «унаследовали» это поведение? В приведенной выше парадигме это невозможно, поскольку только базовая структура ... в этом случае "MyStruct" фактически будет повторять свои поляБиблиотека https://github.com/mitchellh/mapstructure от Hashicorp делает это из коробки:
import "github.com/mitchellh/mapstructure" mapstructure.Decode(myData, &result)
Второй
result
параметр должен быть адресом структуры.источник
user_name
и struct filed естьUserName
?encoding/json
packageпросто например:
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() }
источник
Вы можете это сделать ... это может стать немного некрасивым, и вы столкнетесь с некоторыми пробами и ошибками с точки зрения типов сопоставления ... но вот основная суть этого:
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
источник
reflect: call of reflect.Value.Set on zero Value
Я адаптирую ответ Дэйва и добавляю рекурсивную функцию. Я все еще работаю над более удобной версией. Например, числовая строка на карте должна иметь возможность преобразовывать в 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) }
источник
Есть два шага:
Ниже приведен пример:
источник