将 map 转换为 struct

我试图在 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 作为中介来实现; 是否有其他更有效的方法来实现这一点?

174898 次浏览

你可以这样做... 它可能会变得有点难看,你会面临一些试错在映射类型方面。.但基本要点如下:

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

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

我改编了 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)
}

Hashicorp 的 https://github.com/mitchellh/mapstructure库开箱即用:

import "github.com/mitchellh/mapstructure"


mapstructure.Decode(myData, &result)

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

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

有两个步骤:

  1. 将接口转换为 JSON 字节
  2. 将 JSON Byte 转换为 struct

下面是一个例子:

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

您可以通过 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}
}

这里的函数可以通过标签将 map 转换为 struct。如果标签不存在,它将通过 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
}