Go语言 正在将Map转换为结构

wvmv3b1j  于 2023-01-06  发布在  Go
关注(0)|答案(9)|浏览(169)

我尝试在Go语言中创建一个泛型方法,它将使用map[string]interface{}中的数据填充struct,例如,方法的签名和用法如下所示:

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作为中介来完成;有没有其他更有效的方法?

uinbv5nw

uinbv5nw1#

最简单的方法是使用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)
}
dz6r00yl

dz6r00yl2#

Hashicorp的https://github.com/mitchellh/mapstructure库可以实现以下功能:

import "github.com/mitchellh/mapstructure"

mapstructure.Decode(myData, &result)

第二个result参数必须是结构的地址。

epggiuax

epggiuax3#

  • 最简单的方法是使用encoding/json

仅举个例子:

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

d5vmydt94#

你可以做到这一点...它可能会变得有点难看,你会面临一些试验和错误方面的Map类型...但这里的基本要点:

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

cwtwac6a

cwtwac6a5#

有两个步骤:
1.将接口转换为JSON字节
1.将JSON字节转换为结构
下面是一个例子:

dbByte, _ := json.Marshal(dbContent)
_ = json.Unmarshal(dbByte, &MyStruct)
v09wglhw

v09wglhw6#

可以通过JSON进行往返:

package main

import (
   "bytes"
   "encoding/json"
)

func transcode(in, out interface{}) {
   buf := new(bytes.Buffer)
   json.NewEncoder(buf).Encode(in)
   json.NewDecoder(buf).Decode(out)
}

示例:

package main
import "fmt"

type myStruct struct {
   Name string
   Age  int64
}

func main() {
   myData := map[string]interface{}{
      "Name": "Tony",
      "Age": 23,
   }
   var result myStruct
   transcode(myData, &result)
   fmt.Printf("%+v\n", result) // {Name:Tony Age:23}
}
unftdfkk

unftdfkk7#

我修改了dave的答案,并添加了递归特性,我还在开发一个更加用户友好的版本,例如,map中的数字字符串应该能够转换为struct中的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)
}
5w9g7ksd

5w9g7ksd8#

这里的函数通过标记将Map转换为结构。如果标记不存在,它将通过fieldByName查找。
多亏了https://gist.github.com/lelandbatey/a5c957b537bed39d1d6fb202c3b8de06

type MyStruct struct {
    Name string `json:"name"`
    ID   int    `json:"id"`
}

myStruct := &MyStruct{}

for k, v := range mapToConvert {
        err := MapToStruct(myStruct, k, v)
        if err != nil {
            fmt.Println(err)
        }
    }
func MapToStruct(s interface{}, k string, v interface{}) error {
    var jname string
    structValue := reflect.ValueOf(s).Elem()
    fieldByTagName := func(t reflect.StructTag) (string, error) {
        if jt, ok := t.Lookup("keyname"); ok {
            return strings.Split(jt, ",")[0], nil
        }
        return "", fmt.Errorf("tag provided %s does not define a json tag", k)
    }
    fieldNames := map[string]int{}
    for i := 0; i < structValue.NumField(); i++ {
        typeField := structValue.Type().Field(i)
        tag := typeField.Tag
        if string(tag) == "" {
            jname = toMapCase(typeField.Name)
        } else {
            jname, _ = fieldByTagName(tag)
        }
        fieldNames[jname] = i
    }

    fieldNum, ok := fieldNames[k]
    if !ok {
        return fmt.Errorf("field %s does not exist within the provided item", k)
    }
    fieldVal := structValue.Field(fieldNum)
    fieldVal.Set(reflect.ValueOf(v))

    return nil
}

func toMapCase(s string) (str string) {
    runes := []rune(s)
    for j := 0; j < len(runes); j++ {
        if unicode.IsUpper(runes[j]) == true {
            if j == 0 {
                str += strings.ToLower(string(runes[j]))
            } else {
                str += "_" + strings.ToLower(string(runes[j]))
            }
        } else {
            str += strings.ToLower(string(runes[j]))
        }
    }
    return str
}
vktxenjb

vktxenjb9#

简单的方法就是将其编组为json字符串,然后将其解组为struct
here is the link

相关问题