This article describes the “pitfalls” you need to know when converting struct
to map[string]interface{}
in Go, and also some of the methods you need to know.
We usually use struct
in Go to store our data, for example, to store user information, we might define the following struct
.
1
2
3
4
5
6
7
|
// UserInfo
type UserInfo struct {
Name string `json:"name"`
Age int `json:"age"`
}
u1 := UserInfo{Name: "q1mi", Age: 18}
|
struct to map[string]interface
JSON serialization method
Serialize u1
with JSON
, then deserialize to map
1
2
3
4
5
6
7
8
9
10
|
func main() {
u1 := UserInfo{Name: "q1mi", Age: 18}
b, _ := json.Marshal(&u1)
var m map[string]interface{}
_ = json.Unmarshal(b, &m)
for k, v := range m{
fmt.Printf("key:%v value:%v\n", k, v)
}
}
|
Output:
1
2
|
key:name value:q1mi
key:age value:18
|
It doesn’t seem to be a problem, but there is actually a “pitfall” here. That is, the json
package in the Go language serializes the numeric types (integers, floats, etc.) stored in the null interface to the float64
type.
That is, m["age"]
in the above example is now a float64
at the bottom, not an int
anymore. Let’s verify that.
1
2
3
4
5
6
7
8
9
10
|
func main() {
u1 := UserInfo{Name: "q1mi", Age: 18}
b, _ := json.Marshal(&u1)
var m map[string]interface{}
_ = json.Unmarshal(b, &m)
for k, v := range m{
fmt.Printf("key:%v value:%v value type:%T\n", k, v, v)
}
}
|
Output:
1
2
|
key:name value:q1mi value type:string
key:age value:18 value type:float64
|
Obviously, there was an unexpected behavior and we need to find a way to circumvent it.
Reflection
There is no way to do it yourself. Here we use reflection to generate map
by traversing the structure fields as follows.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
// ToMap struct to Map[string]interface{}
func ToMap(in interface{}, tagName string) (map[string]interface{}, error){
out := make(map[string]interface{})
v := reflect.ValueOf(in)
if v.Kind() == reflect.Ptr {
v = v.Elem()
}
if v.Kind() != reflect.Struct { // Non-structural return error
return nil, fmt.Errorf("ToMap only accepts struct or struct pointer; got %T", v)
}
t := v.Type()
// Traversing structure fields
// Specify the tagName value as the key in the map; the field value as the value in the map
for i := 0; i < v.NumField(); i++ {
fi := t.Field(i)
if tagValue := fi.Tag.Get(tagName); tagValue != "" {
out[tagValue] = v.Field(i).Interface()
}
}
return out, nil
}
|
Validate:
1
2
3
4
|
m2, _ := ToMap(&u1, "json")
for k, v := range m2{
fmt.Printf("key:%v value:%v value type:%T\n", k, v, v)
}
|
Output:
1
2
|
key:name value:q1mi value type:string
key:age value:18 value type:int
|
This time the type of map[“age”] is correct.
Third-party library structs
In addition to implementing it yourself, there are ready-made tools on Github, such as a third-party library: https://github.com/fatih/structs.
The custom structure tag
it uses is structs
:
1
2
3
4
5
|
// UserInfo
type UserInfo struct {
Name string `json:"name" structs:"name"`
Age int `json:"age" structs:"age"`
}
|
The usage is simple:
1
2
3
4
|
m3 := structs.Map(&u1)
for k, v := range m3 {
fmt.Printf("key:%v value:%v value type:%T\n", k, v, v)
}
|
The structs
package also has many other usage examples, so you can check the documentation. However, it should be noted that this library is currently set to read-only by the author.
nested struct to map[string]interface
structs
itself supports nested structs to map[string]interface{}
, and it will convert to map[string]interface{}
nested in map[string]interface{}
when it encounters a nested struct.
We define a set of nested structs as follows:
1
2
3
4
5
6
7
8
9
10
11
|
// UserInfo
type UserInfo struct {
Name string `json:"name" structs:"name"`
Age int `json:"age" structs:"age"`
Profile `json:"profile" structs:"profile"`
}
// Profile
type Profile struct {
Hobby string `json:"hobby" structs:"hobby"`
}
|
Declare the structure variable u1
:
1
|
u1 := UserInfo{Name: "q1mi", Age: 18, Profile: Profile{"Two Color Ball"}}
|
Third-party library structs
The code is actually the same as above:
1
2
3
4
|
m3 := structs.Map(&u1)
for k, v := range m3 {
fmt.Printf("key:%v value:%v value type:%T\n", k, v, v)
}
|
Output:
1
2
3
|
key:name value:q1mi value type:string
key:age value:18 value type:int
key:profile value:map[hobby:Two Color Ball] value type:map[string]interface {}
|
From the result, the last nested field profile
is map[string]interface {}
, which is a map
nested map
.
Use reflection to convert to single layer map
What if we want to convert a nested structure into a single level map
?
Let’s take the above reflected code and make some simple changes:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
|
// ToMap2 Converting structures to single-level maps
func ToMap2(in interface{}, tag string) (map[string]interface{}, error) {
// The current function only receives struct types
v := reflect.ValueOf(in)
if v.Kind() == reflect.Ptr { // Structure Pointer
v = v.Elem()
}
if v.Kind() != reflect.Struct {
return nil, fmt.Errorf("ToMap only accepts struct or struct pointer; got %T", v)
}
out := make(map[string]interface{})
queue := make([]interface{}, 0, 1)
queue = append(queue, in)
for len(queue) > 0 {
v := reflect.ValueOf(queue[0])
if v.Kind() == reflect.Ptr { // Structure Pointer
v = v.Elem()
}
queue = queue[1:]
t := v.Type()
for i := 0; i < v.NumField(); i++ {
vi := v.Field(i)
if vi.Kind() == reflect.Ptr { // Embedded Pointer
vi = vi.Elem()
if vi.Kind() == reflect.Struct { // Structures
queue = append(queue, vi.Interface())
} else {
ti := t.Field(i)
if tagValue := ti.Tag.Get(tag); tagValue != "" {
// Save to map
out[tagValue] = vi.Interface()
}
}
break
}
if vi.Kind() == reflect.Struct { // Embedded Structs
queue = append(queue, vi.Interface())
break
}
// General Fields
ti := t.Field(i)
if tagValue := ti.Tag.Get(tag); tagValue != "" {
// Save to map
out[tagValue] = vi.Interface()
}
}
}
return out, nil
}
|
To test:
1
2
3
4
|
m4, _ := ToMap2(&u1, "json")
for k, v := range m4 {
fmt.Printf("key:%v value:%v value type:%T\n", k, v, v)
}
|
Output:
1
2
3
|
key:name value:q1mi value type:string
key:age value:18 value type:int
key:hobby value:Two Color Ball value type:string
|
This converts the nested structure to a single-level map, but note that the fields of the structure and the nested structure in this scenario will need to avoid duplication.
Reference https://www.liwenzhou.com/posts/Go/struct2map/