-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathset.go
137 lines (110 loc) · 3.47 KB
/
set.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
package dot
import (
"encoding/json"
"errors"
"github.com/oleiade/reflections"
"reflect"
"strings"
)
// Set will apply the value specified by the value argument at the "position"/attribute in the provided obj argument.
// It will allocate map[string]interface{} for any missing nodes in the "tree" generated by addressing. Returns an error
// if it cannot apply the provided value for any reason.
func Set(obj interface{}, prop string, value interface{}) error {
if obj == nil {
return errors.New("obj may not be nil for dot.Set")
}
// trim outer spaces from property
prop = strings.TrimSpace(prop)
// validate obvious pathing errors
if len(prop) > 0 {
if prop[0] == '.' {
return errors.New("dot-set property may not start with '.'")
}
if prop[len(prop)-1] == '.' {
return errors.New("dot-set property may not end in '.'")
}
}
// get the array access
// TODO: improve escape mechanism for \.
arr := strings.Split(strings.ReplaceAll(prop, "\\.", "\a"), ".")
// adjust a struct to be a map TODO: improve for performance concerns (and code cleanliness)
effReflect := reflect.TypeOf(obj)
if effReflect.Kind() == reflect.Ptr && effReflect.Elem().Kind() == reflect.Struct {
field := strings.ReplaceAll(arr[0], "\a", ".")
if len(arr) == 1 {
return setProperty(obj, field, value)
}
fieldVal := reflect.ValueOf(obj).Elem().FieldByName(field)
fieldType := reflect.TypeOf(fieldVal.Interface())
// missing maps must be allocated with MakeMap
if fieldType.Kind() == reflect.Map && fieldVal.IsNil() {
newMap := reflect.MakeMap(fieldType).Interface()
if err := setProperty(obj, field, newMap); err != nil {
return err
}
return Set(newMap, strings.Join(arr[1:], "."), value)
}
return Set(fieldVal.Addr().Interface(), strings.Join(arr[1:], "."), value)
}
var err error
var key string
var fullMap map[string]interface{}
var effectiveObj = obj
var deepestSetObj = obj
var tempObj interface{}
var deepestSetPathIndex int
last, arr := arr[len(arr)-1], arr[:len(arr)-1]
last = strings.ReplaceAll(last, "\a", ".")
// get each level of property, all the way down to the leaf
for deepestSetPathIndex, key = range arr {
tempObj, err = getProperty(effectiveObj, strings.ReplaceAll(key, "\a", "\\."))
if err != nil {
break
}
if tempObj == nil {
effectiveObj = nil
break
}
effectiveObj = tempObj
deepestSetObj = tempObj
}
// if we need to allocate all the way down to the object
if effectiveObj == nil {
if err := setProperty(deepestSetObj, key, make(map[string]interface{})); err != nil {
return err
}
innerProp, err := getProperty(deepestSetObj, key)
if err != nil {
return err
}
propPath := strings.Split(prop, ".")
return Set(innerProp, strings.Join(propPath[deepestSetPathIndex+1:], "."), value)
}
if fullMap != nil {
err := setProperty(effectiveObj, last, value)
if err != nil {
return err
}
b, err := json.Marshal(fullMap)
if err != nil {
return err
}
if err := json.Unmarshal(b, &obj); err != nil {
return err
}
return nil
}
return setProperty(effectiveObj, last, value)
}
func setProperty(obj interface{}, prop string, val interface{}) error {
if reflect.TypeOf(obj).Kind() == reflect.Map {
value := reflect.ValueOf(obj)
value.SetMapIndex(reflect.ValueOf(prop), reflect.ValueOf(val))
return nil
}
if reflect.TypeOf(obj).Kind() != reflect.Ptr {
return errors.New("object must be a pointer to a struct")
}
prop = strings.Title(prop)
return reflections.SetField(obj, prop, val)
}