将地图转换为结构体

huangapple go评论101阅读模式
英文:

Converting map to 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的Name被设置为"Tony",Age被设置为23

我知道可以使用JSON作为中介来实现这一点;是否有其他更高效的方法来做到这一点?

英文:

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 and usage might look like:

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

I know this can be done using JSON as an intermediary; is there another more efficient way of doing this?

答案1

得分: 175

最简单的方法是使用https://github.com/mitchellh/mapstructure库。

import "github.com/mitchellh/mapstructure"

mapstructure.Decode(myData, &result)

如果你想自己实现,可以像这样做:

func SetField(obj interface{}, name string, value interface{}) error {
    structValue := reflect.ValueOf(obj).Elem()
    structFieldValue := structValue.FieldByName(name)

    if !structFieldValue.IsValid() {
        return fmt.Errorf("obj中没有字段:%s", name)
    }

    if !structFieldValue.CanSet() {
        return fmt.Errorf("无法设置字段值:%s", name)
    }

    structFieldType := structFieldValue.Type()
    val := reflect.ValueOf(value)
    if structFieldType != val.Type() {
        return errors.New("提供的值类型与obj字段类型不匹配")
    }

    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)
}
英文:

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

答案2

得分: 90

Hashicorp的https://github.com/mitchellh/mapstructure库可以直接实现这个功能:

import "github.com/mitchellh/mapstructure"

mapstructure.Decode(myData, &result)

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

英文:

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.

答案3

得分: 75

最简单的方法是使用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 {
        // 错误处理
        fmt.Println(err)
        return
    }

    student := Student{}
    if err := json.Unmarshal(jsonbody, &student); err != nil {
        // 错误处理
        fmt.Println(err)
        return
    }

    fmt.Printf("%#v\n", student)
}

func main() {
    Test()
}
英文:
  • 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()
}

答案4

得分: 16

你可以做到...可能会有一些混乱,并且在类型映射方面会遇到一些试错问题...但这是基本的要点:

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

英文:

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

答案5

得分: 13

有两个步骤:

  1. 将接口转换为 JSON 字节
  2. 将 JSON 字节转换为结构体

以下是一个示例:

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

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)

答案6

得分: 8

你可以通过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}
}
英文:

You can roundtrip it through 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)
}

Example:

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

答案7

得分: 1

我改编了Dave的答案,并添加了递归功能。我仍在努力开发一个更用户友好的版本。例如,映射中的数字字符串应该能够转换为结构体中的整数。

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("obj中没有字段:%s", name)
	}

	if !fieldVal.CanSet() {
		return fmt.Errorf("无法设置字段值:%s", name)
	}

	val := reflect.ValueOf(value)

	if fieldVal.Type() != val.Type() {

		if m, ok := value.(map[string]interface{}); ok {

			// 如果字段值是结构体
			if fieldVal.Kind() == reflect.Struct {
				return FillStruct(m, fieldVal.Addr().Interface())
			}

			// 如果字段值是指向结构体的指针
			if fieldVal.Kind() == reflect.Ptr && fieldVal.Type().Elem().Kind() == reflect.Struct {
				if fieldVal.IsNil() {
					fieldVal.Set(reflect.New(fieldVal.Type().Elem()))
				}
				// fmt.Printf("递归:%v %v\n", m, fieldVal.Interface())
				return FillStruct(m, fieldVal.Interface())
			}

		}

		return fmt.Errorf("提供的值类型与obj字段类型不匹配")
	}

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

这段代码实现了一个函数FillStruct,它可以将一个map中的值填充到一个结构体中。结构体的字段名与map中的键名对应。如果字段的类型与map中对应键的值类型不匹配,函数会尝试进行递归填充。在main函数中,我们创建了一个map,然后将其填充到MyStruct类型的变量result中,并打印结果。

英文:

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

答案8

得分: 1

以下是将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("json"); 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
}

这段代码是一个将map转换为struct的函数。它通过遍历map中的键值对,将值赋给对应的struct字段。函数中使用了反射来获取struct的字段信息,并根据标签来确定字段名。如果标签不存在,则使用驼峰命名法将字段名转换为下划线命名法。

英文:

Here function to convert map to struct by tag. If tag not exist it will find by fieldByName.

Thanks to https://gist.github.com/lelandbatey/a5c957b537bed39d1d6fb202c3b8de06

type MyStruct struct {
Name string `json:&quot;name&quot;`
ID   int    `json:&quot;id&quot;`
}
myStruct := &amp;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(&quot;keyname&quot;); ok {
return strings.Split(jt, &quot;,&quot;)[0], nil
}
return &quot;&quot;, fmt.Errorf(&quot;tag provided %s does not define a json tag&quot;, k)
}
fieldNames := map[string]int{}
for i := 0; i &lt; structValue.NumField(); i++ {
typeField := structValue.Type().Field(i)
tag := typeField.Tag
if string(tag) == &quot;&quot; {
jname = toMapCase(typeField.Name)
} else {
jname, _ = fieldByTagName(tag)
}
fieldNames[jname] = i
}
fieldNum, ok := fieldNames[k]
if !ok {
return fmt.Errorf(&quot;field %s does not exist within the provided item&quot;, 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 &lt; len(runes); j++ {
if unicode.IsUpper(runes[j]) == true {
if j == 0 {
str += strings.ToLower(string(runes[j]))
} else {
str += &quot;_&quot; + strings.ToLower(string(runes[j]))
}
} else {
str += strings.ToLower(string(runes[j]))
}
}
return str
}

答案9

得分: 0

简单的方法是将其转换为JSON字符串,然后将其解析为结构体。

这是链接

英文:

Simple way just marshal it json string
and then unmarshat it to struct

here is the link

huangapple
  • 本文由 发表于 2014年11月5日 04:56:40
  • 转载请务必保留本文链接:https://go.coder-hub.com/26744873.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定