-
Notifications
You must be signed in to change notification settings - Fork 0
/
to_map.go
100 lines (81 loc) · 3.1 KB
/
to_map.go
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
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
package priv
import (
"reflect"
"github.com/fatih/structs"
"github.com/joeycumines/go-dotnotation/dotnotation"
)
// ToMap converts the item (struct or array of structs) to a map.
// Only the fields in the fieldsToInclude list are included.
// This list supports the dot operator to access nested fields, as well as the -> operator to rename a nested field.
// This function panics if the field cannot be found or set.
// If you are calling this function with a dynamically built list of fieldsToInclude, you may want to use the priv.ToMapErr alternative.
//
// eg.
// users := [{ID: "123", Some: {Nested: {Field: "abc"}}, SomethingElse: true}]
// ToMap(users, "ID", "Some.Nested.Field->Renamed.Location")
// would result in:
// [{ID: "123", Renamed: {Location: "abc"}}]
func ToMap(item interface{}, fieldsToInclude ...string) interface{} {
value, err := ToMapErr(item, fieldsToInclude...)
if err != nil {
panic("priv.ToMap: " + err.Error())
}
return value
}
// ToMapErr does the same thing as priv.ToMap, but returns an error if the field cannot be found or set.
// Most of the time panicing (as priv.ToMap does) is fine, since the fields are set before compiling, so a panic to warn of a typo is not a big deal.
func ToMapErr(item interface{}, fieldsToInclude ...string) (interface{}, error) {
reflectItem := reflect.ValueOf(item)
// If it is an array or slice
if reflectItem.Kind() == reflect.Slice || reflectItem.Kind() == reflect.Array {
result := []interface{}{}
// Iterate over it, to convert each item to a map
for i := 0; i < reflectItem.Len(); i++ {
value, err := toMap(reflectItem.Index(i), fieldsToInclude...)
if err != nil {
return nil, err
}
result = append(result, value)
}
return result, nil
}
// Otherwise, convert the item to a map
return toMap(reflectItem, fieldsToInclude...)
}
func toMap(item reflect.Value, fieldsToInclude ...string) (interface{}, error) {
mappedFieldsToInclude := []mapField{}
// Convert the field strings to mapped fields
for _, field := range fieldsToInclude {
mappedFieldsToInclude = append(mappedFieldsToInclude, mapFieldFromFieldString(field))
}
return toMapV(item, mappedFieldsToInclude...)
}
func toMapV(item reflect.Value, mappedFieldsToInclude ...mapField) (interface{}, error) {
// Convert the struct to a map
sMap := structs.Map(item.Interface())
sMapAccessor := dotnotation.Accessor{}
resultAccessor := dotnotation.Accessor{
Getter: func(target interface{}, property string) (interface{}, error) {
// Getter for result automatically adds another layer to the nested maps if required
rMap, _ := target.(map[string]interface{})
if rMap[property] == nil {
rMap[property] = make(map[string]interface{})
}
return rMap[property], nil
},
}
// Build the result
result := make(map[string]interface{})
for _, field := range mappedFieldsToInclude {
// Grab the value
value, err := sMapAccessor.Get(sMap, field.FromLocation())
if err != nil {
return nil, err
}
// Set the value in the results
if err := resultAccessor.Set(result, field.ToLocation(), value); err != nil {
return nil, err
}
}
return result, nil
}