Converting map to struct

前端 未结 6 1344
灰色年华
灰色年华 2020-12-04 05:46

I am trying to create a generic method in Go that will fill a struct using data from a map[string]interface{}. For example, the method signature an

相关标签:
6条回答
  • 2020-12-04 06:28

    There are two steps:

    1. Convert interface to JSON Byte
    2. Convert JSON Byte to struct

    Below is an example:

    dbByte, _ := json.Marshal(dbContent)
    _ = json.Unmarshal(dbByte, &MyStruct)
    
    0 讨论(0)
  • 2020-12-04 06:31
    • the simplest way to do that is using encoding/json package

    just for example:

    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()
    }
    
    0 讨论(0)
  • 2020-12-04 06:36

    The simplest way would be to use https://github.com/mitchellh/mapstructure

    import "github.com/mitchellh/mapstructure"
    
    mapstructure.Decode(myData, &result)
    

    If you want to do it yourself, you could do something like this:

    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)
    }
    
    0 讨论(0)
  • 2020-12-04 06:38

    I adapt dave's answer, and add a recursive feature. I'm still working on a more user friendly version. For example, a number string in the map should be able to be converted to int in the struct.

    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 讨论(0)
  • 2020-12-04 06:40

    You can do it ... it may get a bit ugly and you'll be faced with some trial and error in terms of mapping types .. but heres the basic gist of it:

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

    Working sample: http://play.golang.org/p/PYHz63sbvL

    0 讨论(0)
  • 2020-12-04 06:41

    Hashicorp's https://github.com/mitchellh/mapstructure library does this out of the box:

    import "github.com/mitchellh/mapstructure"
    
    mapstructure.Decode(myData, &result)
    

    The second result parameter has to be an address of the struct.

    0 讨论(0)
提交回复
热议问题