函数将 struct 转换为 Golang 的 map

我想把 struct 转换成 Golang 的 map。如果我可以使用 JSON 标记作为创建的映射中的键(否则默认为字段名) ,那也是很好的。

编辑2020年12月14日

由于 结构回购已归档,因此可以使用 地图结构代替。

编辑 TL; DR 版本,2015年6月15日

如果您想要将结构转换为映射的快速解决方案,请参见 接受的答案,对它进行升级并使用该包。

编程愉快! :)


原文

到目前为止我有这个功能,我正在使用反射包,但我不明白如何使用包,请容忍我。

func ConvertToMap(model interface{}) bson.M {
ret := bson.M{}


modelReflect := reflect.ValueOf(model)


if modelReflect.Kind() == reflect.Ptr {
modelReflect = modelReflect.Elem()
}


modelRefType := modelReflect.Type()
fieldsCount := modelReflect.NumField()


var fieldData interface{}


for i := 0; i < fieldsCount; i++ {
field := modelReflect.Field(i)


switch field.Kind() {
case reflect.Struct:
fallthrough
case reflect.Ptr:
fieldData = ConvertToMap(field.Interface())
default:
fieldData = field.Interface()
}


ret[modelRefType.Field(i).Name] = fieldData
}


return ret
}

我还查看了 JSON 包的源代码,因为它应该包含我所需要的实现(或者部分实现) ,但是不太理解。

163769 次浏览

Here is a function I've written in the past to convert a struct to a map, using tags as keys

// ToMap converts a struct to a map using the struct's tags.
//
// ToMap uses tags on struct fields to decide which fields to add to the
// returned map.
func ToMap(in interface{}, tag string) (map[string]interface{}, error){
out := make(map[string]interface{})


v := reflect.ValueOf(in)
if v.Kind() == reflect.Ptr {
v = v.Elem()
}


// we only accept structs
if v.Kind() != reflect.Struct {
return nil, fmt.Errorf("ToMap only accepts structs; got %T", v)
}


typ := v.Type()
for i := 0; i < v.NumField(); i++ {
// gets us a StructField
fi := typ.Field(i)
if tagv := fi.Tag.Get(tag); tagv != "" {
// set key of map to value in struct field
out[tagv] = v.Field(i).Interface()
}
}
return out, nil
}

Runnable example here.

Note, if you have multiple fields with the same tag value, then you will obviously not be able to store them all within a map. It might be prudent to return an error if that happens.

I also had need for something like this. I was using an internal package which was converting a struct to a map. I decided to open source it with other struct based high level functions. Have a look:

https://github.com/fatih/structs

It has support for:

  • Convert struct to a map
  • Extract the fields of a struct to a []string
  • Extract the values of a struct to a []values
  • Check if a struct is initialized or not
  • Check if a passed interface is a struct or a pointer to struct

You can see some examples here: http://godoc.org/github.com/fatih/structs#pkg-examples For example converting a struct to a map is a simple:

type Server struct {
Name    string
ID      int32
Enabled bool
}


s := &Server{
Name:    "gopher",
ID:      123456,
Enabled: true,
}


// => {"Name":"gopher", "ID":123456, "Enabled":true}
m := structs.Map(s)

The structs package has support for anonymous (embedded) fields and nested structs. The package provides to filter certain fields via field tags.

From struct to map[string]interface{}

package main


import (
"fmt"
"encoding/json"
)


type MyData struct {
One   int
Two   string
Three int
}


func main() {
in := &MyData{One: 1, Two: "second"}


var inInterface map[string]interface{}
inrec, _ := json.Marshal(in)
json.Unmarshal(inrec, &inInterface)


// iterate through inrecs
for field, val := range inInterface {
fmt.Println("KV Pair: ", field, val)
}
}

go playground here

package main


import (
"fmt"
"reflect"
)


type bill struct {
N1 int
N2 string
n3 string
}


func main() {
a := bill{4, "dhfthf", "fdgdf"}


v := reflect.ValueOf(a)


values := make(map[string]interface{}, v.NumField())


for i := 0; i < v.NumField(); i++ {
if v.Field(i).CanInterface() {
values[v.Type().Field(i).Name] = v.Field(i).Interface()
} else {
fmt.Printf("sorry you have a unexported field (lower case) value you are trying to sneak past. I will not allow it: %v\n", v.Type().Field(i).Name)
}
}


fmt.Println(values)


passObject(&values)
}


func passObject(v1 *map[string]interface{}) {
fmt.Println("yoyo")
}

I like the importable package for the accepted answer, but it does not translate my json aliases. Most of my projects have a helper function/class that I import.

Here is a function that solves my specific problem.


// Converts a struct to a map while maintaining the json alias as keys
func StructToMap(obj interface{}) (newMap map[string]interface{}, err error) {
data, err := json.Marshal(obj) // Convert to a json string


if err != nil {
return
}


err = json.Unmarshal(data, &newMap) // Convert to a map
return
}


And in the main, this is how it would be called...

package main


import (
"fmt"
"encoding/json"
"github.com/fatih/structs"
)


type MyStructObject struct {
Email string `json:"email_address"`
}


func main() {
obj := &MyStructObject{Email: "test@test.com"}


// My solution
fmt.Println(StructToMap(obj)) // prints {"email_address": "test@test.com"}


// The currently accepted solution
fmt.Println(structs.Map(obj)) // prints {"Email": "test@test.com"}
}
map := Structpb.AsMap()


// map is the map[string]interface{}

I'm a bit late but I needed this kind of feature so I wrote this. Can resolve nested structs. By default, uses field names but can also use custom tags. A side effect is that if you set the tagTitle const to json, you could use the json tags you already have.

package main


import (
"fmt"
"reflect"
)


func StructToMap(val interface{}) map[string]interface{} {
//The name of the tag you will use for fields of struct
const tagTitle = "kelvin"


var data map[string]interface{} = make(map[string]interface{})
varType := reflect.TypeOf(val)
if varType.Kind() != reflect.Struct {
// Provided value is not an interface, do what you will with that here
fmt.Println("Not a struct")
return nil
}


value := reflect.ValueOf(val)
for i := 0; i < varType.NumField(); i++ {
if !value.Field(i).CanInterface() {
//Skip unexported fields
continue
}
tag, ok := varType.Field(i).Tag.Lookup(tagTitle)
var fieldName string
if ok && len(tag) > 0 {
fieldName = tag
} else {
fieldName = varType.Field(i).Name
}
if varType.Field(i).Type.Kind() != reflect.Struct {
data[fieldName] = value.Field(i).Interface()
} else {
data[fieldName] = StructToMap(value.Field(i).Interface())
}


}


return data
}