-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathto_struct.go
141 lines (114 loc) · 3.29 KB
/
to_struct.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
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
package structmapper
import (
"encoding/json"
"fmt"
"reflect"
"time"
)
// StringMapToStruct is the inverse of StructToStringMap.
// If strict is false, some attempts are made to coerce non-json inputs:
// - bools are checked for natural values (1 or begins with 't')
// - string slices are invented by splitting the string on commas
//
// Note: bool conversion is non-strict in all cases.
func StringMapToStruct(inputMap map[string]string, str interface{}, strict bool) error {
if reflect.TypeOf(str).Kind() != reflect.Ptr || reflect.Indirect(reflect.ValueOf(str)).Kind() != reflect.Struct {
return fmt.Errorf("second param must be a pointer to a struct")
}
if inputMap == nil {
return fmt.Errorf("first param must not be nil")
}
// outputMap is an intermediate form of the data with each value being a string
// or a json decoded value
outputMap := make(map[string]interface{}, len(inputMap))
var walkValue func(reflect.Value) error
// Iterate over the given struct and collect values into the map.
// Anonymous fields cause a recursive call to walkValue().
walkValue = func(sv reflect.Value) error {
st := sv.Type()
// Iterate over the struct looking for matches in the string map.
for i := 0; i < st.NumField(); i++ {
field := st.Field(i)
ft := st.Field(i)
if ft.PkgPath != "" { // unexported
continue
}
if field.Anonymous {
if err := walkValue(sv.Field(i)); err != nil {
return err
}
continue
}
tag, _, omit := getJSONTagFromField(field)
if omit {
continue
}
if value, exists := inputMap[tag]; exists {
if err := convert(outputMap, field, sv.Field(i), tag, value, strict); err != nil {
return err
}
}
}
return nil
}
if err := walkValue(reflect.ValueOf(str).Elem()); err != nil {
return err
}
if err := encodeMaps(outputMap, str); err != nil {
return err
}
return nil
}
func encodeMaps(outputMap map[string]interface{}, str interface{}) error {
// we now json encode outputMap to make it a form which looks more like str
buf, err := json.Marshal(outputMap)
if err != nil {
return fmt.Errorf("json encoding: %w", err)
}
// json decode fully into str
if err := json.Unmarshal(buf, &str); err != nil {
return fmt.Errorf("json decoding: %w", err)
}
return nil
}
func convert(
outputMap map[string]interface{},
field reflect.StructField,
fieldValue reflect.Value,
tag, value string,
strict bool,
) error {
if field.Type.Kind() == reflect.String {
outputMap[tag] = value
return nil
}
if _, isTimeVariable := isTime(fieldValue); isTimeVariable {
if value != "" {
tval, err := time.Parse(time.RFC3339, value)
if err != nil {
return fmt.Errorf("parsing time value: %w", err)
}
outputMap[tag] = tval
}
return nil
}
if field.Type.Kind() == reflect.Bool {
outputMap[tag] = StringToBool(value)
return nil
}
if value == "" {
return nil
}
var decodedValue interface{}
if err := json.Unmarshal([]byte(value), &decodedValue); err != nil && strict {
return fmt.Errorf("json decoding: %w", err)
} else if err == nil { // no error, so all good to return
outputMap[tag] = decodedValue
return nil
}
// not in strict mode, so try to find a value worth returning
if field.Type == reflect.SliceOf(reflect.TypeOf("")) {
outputMap[tag] = stringToStringSlice(value)
}
return nil
}