In Golang, how to convert a structure into a map? This article introduces two methods. The first is to use the json package to parse, decoding and encoding. The second type is to use reflection, which is more efficient.The code is here. If you find the code useful, you can give my code repository a star.
Suppose there is a structure below
func newUser() User { name := "user" MyGithub := GithubPage{ URL: "/liangyaopei", Star: 1, } NoDive := StructNoDive{NoDive: 1} dateStr := "2020-07-21 12:00:00" date, _ := (timeLayout, dateStr) profile := Profile{ Experience: "my experience", Date: date, } return User{ Name: name, Github: MyGithub, NoDive: NoDive, MyProfile: profile, } } type User struct { Name string `map:"name,omitempty"` // string Github GithubPage `map:"github,dive,omitempty"` // struct dive NoDive StructNoDive `map:"no_dive,omitempty"` // no dive struct MyProfile Profile `map:"my_profile,omitempty"` // struct implements its own method } type GithubPage struct { URL string `map:"url"` Star int `map:"star"` } type StructNoDive struct { NoDive int } type Profile struct { Experience string `map:"experience"` Date `map:"time"` } // its own toMap method func (p Profile) StructToMap() (key string, value interface{}) { return "time", (timeLayout) }
marshal, unmarshal of json package
First serialize the structure into []byte array, and then serialize it from the []byte array into a structure.
data, _ := (&user) m := make(map[string]interface{}) (data, &m)
Advantages
Simple to use Disadvantages
Slow efficiency
It cannot support some customized keys, nor can it support some customized methods, such as expanding the domain of struct.
Use reflection
This paper implements a method of converting structures into maps using reflection. Through tags and reflections, the result returned by newUser() in the above example is converted into the following map.
It contains the expansion of the domain that customizes the struct.
map[string]interface{}{ "name": "user", "no_dive": StructNoDive{NoDive: 1}, // dive struct field "url": "/liangyaopei", "star": 1, // customized method "time": "2020-07-21 12:00:00", }
Implementation ideas & source code analysis
1. Label recognition.
Use the readTag method to read the label of the field. If there is no label, use the name of the field. Then read the options in the tag. Currently supported 3 options
'-': Ignore the current domain
'omitempty' : When the value of this field is empty, ignore this field
'dive': Recursively traverse this structure and use all fields as keys
If an option is selected, the corresponding binary position of this field is 1.
const ( OptIgnore = "-" OptOmitempty = "omitempty" OptDive = "dive" ) const ( flagIgnore = 1 << iota flagOmiEmpty flagDive ) func readTag(f , tag string) (string, int) { val, ok := (tag) fieldTag := "" flag := 0 // no tag, use field name if !ok { return , flag } opts := (val, ",") fieldTag = opts[0] for i := 1; i < len(opts); i++ { switch opts[i] { case OptIgnore: flag |= flagIgnore case OptOmitempty: flag |= flagOmiEmpty case OptDive: flag |= flagDive } } return fieldTag, flag }
2. Traversal of the field of the structure.
Iterate through each field of the structure and determine the type of field (kind). If it is a basic type of string, int, etc., take the value directly and use the value in the label as the key.
for i := 0; i < (); i++ { ... switch () { case reflect.Int8, reflect.Int16, reflect.Int32, , reflect.Int64: res[tagVal] = () case reflect.Uint8, reflect.Uint16, reflect.Uint32, , reflect.Uint64: res[tagVal] = () case reflect.Float32, reflect.Float64: res[tagVal] = () case : res[tagVal] = () case : res[tagVal] = () default: } } }
3. Transformation of embedded structures
If it is a structure, first check whether there is a method that implements the incoming parameters. If it is implemented, call this method. If it is not implemented, the StructToMap method is called recursively, and then the return result is written to the map of res based on whether it is expanded (dive).
for i := 0; i < (); i++ { fieldType := (i) // ignore unexported field if != "" { continue } // read tag tagVal, flag := readTag(fieldType, tag) if flag&flagIgnore != 0 { continue } fieldValue := (i) if flag&flagOmiEmpty != 0 && () { continue } // ignore nil pointer in field if () == && () { continue } if () == { fieldValue = () } // get kind switch () { case : _, ok := ().MethodByName(methodName) if ok { key, value, err := callFunc(fieldValue, methodName) if err != nil { return nil, err } res[key] = value continue } // recursive deepRes, deepErr := StructToMap((), tag, methodName) if deepErr != nil { return nil, deepErr } if flag&flagDive != 0 { for k, v := range deepRes { res[k] = v } } else { res[tagVal] = deepRes } default: } } ... } // call function func callFunc(fv , methodName string) (string, interface{}, error) { methodRes := (methodName).Call([]{}) if len(methodRes) != methodResNum { return "", nil, ("wrong method %s, should have 2 output: (string,interface{})", methodName) } if methodRes[0].Kind() != { return "", nil, ("wrong method %s, first output should be string", methodName) } key := methodRes[0].String() return key, methodRes[1], nil }
,Slice type conversion
If it is an array, slice type, similarly, check whether there is a method that implements the incoming parameters. If it is implemented, call this method. If not implemented, use the field tag as key and the field value as value.
switch () { case , : _, ok := ().MethodByName(methodName) if ok { key, value, err := callFunc(fieldValue, methodName) if err != nil { return nil, err } res[key] = value continue } res[tagVal] = fieldValue .... }
5. Other types
For other types, such as embedded maps, directly return the resulting value.
switch () { ... case : res[tagVal] = fieldValue case : res[tagVal] = fieldValue case : res[tagVal] = () default: }
The above is personal experience. I hope you can give you a reference and I hope you can support me more. If there are any mistakes or no complete considerations, I would like to give you advice.