1 package objx
2 3 import (
4 "reflect"
5 "regexp"
6 "strconv"
7 "strings"
8 )
9 10 const (
11 // PathSeparator is the character used to separate the elements
12 // of the keypath.
13 //
14 // For example, `location.address.city`
15 PathSeparator string = "."
16 17 // arrayAccessRegexString is the regex used to extract the array number
18 // from the access path
19 arrayAccessRegexString = `^(.+)\[([0-9]+)\]$`
20 21 // mapAccessRegexString is the regex used to extract the map key
22 // from the access path
23 mapAccessRegexString = `^([^\[]*)\[([^\]]+)\](.*)$`
24 )
25 26 // arrayAccessRegex is the compiled arrayAccessRegexString
27 var arrayAccessRegex = regexp.MustCompile(arrayAccessRegexString)
28 29 // mapAccessRegex is the compiled mapAccessRegexString
30 var mapAccessRegex = regexp.MustCompile(mapAccessRegexString)
31 32 // Get gets the value using the specified selector and
33 // returns it inside a new Obj object.
34 //
35 // If it cannot find the value, Get will return a nil
36 // value inside an instance of Obj.
37 //
38 // Get can only operate directly on map[string]interface{} and []interface.
39 //
40 // # Example
41 //
42 // To access the title of the third chapter of the second book, do:
43 //
44 // o.Get("books[1].chapters[2].title")
45 func (m Map) Get(selector string) *Value {
46 rawObj := access(m, selector, nil, false)
47 return &Value{data: rawObj}
48 }
49 50 // Set sets the value using the specified selector and
51 // returns the object on which Set was called.
52 //
53 // Set can only operate directly on map[string]interface{} and []interface
54 //
55 // # Example
56 //
57 // To set the title of the third chapter of the second book, do:
58 //
59 // o.Set("books[1].chapters[2].title","Time to Go")
60 func (m Map) Set(selector string, value interface{}) Map {
61 access(m, selector, value, true)
62 return m
63 }
64 65 // getIndex returns the index, which is hold in s by two branches.
66 // It also returns s without the index part, e.g. name[1] will return (1, name).
67 // If no index is found, -1 is returned
68 func getIndex(s string) (int, string) {
69 arrayMatches := arrayAccessRegex.FindStringSubmatch(s)
70 if len(arrayMatches) > 0 {
71 // Get the key into the map
72 selector := arrayMatches[1]
73 // Get the index into the array at the key
74 // We know this can't fail because arrayMatches[2] is an int for sure
75 index, _ := strconv.Atoi(arrayMatches[2])
76 return index, selector
77 }
78 return -1, s
79 }
80 81 // getKey returns the key which is held in s by two brackets.
82 // It also returns the next selector.
83 func getKey(s string) (string, string) {
84 selSegs := strings.SplitN(s, PathSeparator, 2)
85 thisSel := selSegs[0]
86 nextSel := ""
87 88 if len(selSegs) > 1 {
89 nextSel = selSegs[1]
90 }
91 92 mapMatches := mapAccessRegex.FindStringSubmatch(s)
93 if len(mapMatches) > 0 {
94 if _, err := strconv.Atoi(mapMatches[2]); err != nil {
95 thisSel = mapMatches[1]
96 nextSel = "[" + mapMatches[2] + "]" + mapMatches[3]
97 98 if thisSel == "" {
99 thisSel = mapMatches[2]
100 nextSel = mapMatches[3]
101 }
102 103 if nextSel == "" {
104 selSegs = []string{"", ""}
105 } else if nextSel[0] == '.' {
106 nextSel = nextSel[1:]
107 }
108 }
109 }
110 111 return thisSel, nextSel
112 }
113 114 // access accesses the object using the selector and performs the
115 // appropriate action.
116 func access(current interface{}, selector string, value interface{}, isSet bool) interface{} {
117 thisSel, nextSel := getKey(selector)
118 119 indexes := []int{}
120 for strings.Contains(thisSel, "[") {
121 prevSel := thisSel
122 index := -1
123 index, thisSel = getIndex(thisSel)
124 indexes = append(indexes, index)
125 if prevSel == thisSel {
126 break
127 }
128 }
129 130 if curMap, ok := current.(Map); ok {
131 current = map[string]interface{}(curMap)
132 }
133 // get the object in question
134 switch current.(type) {
135 case map[string]interface{}:
136 curMSI := current.(map[string]interface{})
137 if nextSel == "" && isSet {
138 curMSI[thisSel] = value
139 return nil
140 }
141 142 _, ok := curMSI[thisSel].(map[string]interface{})
143 if !ok {
144 _, ok = curMSI[thisSel].(Map)
145 }
146 147 if (curMSI[thisSel] == nil || !ok) && len(indexes) == 0 && isSet {
148 curMSI[thisSel] = map[string]interface{}{}
149 }
150 151 current = curMSI[thisSel]
152 default:
153 current = nil
154 }
155 156 // do we need to access the item of an array?
157 if len(indexes) > 0 {
158 num := len(indexes)
159 for num > 0 {
160 num--
161 index := indexes[num]
162 indexes = indexes[:num]
163 if array, ok := interSlice(current); ok {
164 if index < len(array) {
165 current = array[index]
166 } else {
167 current = nil
168 break
169 }
170 }
171 }
172 }
173 174 if nextSel != "" {
175 current = access(current, nextSel, value, isSet)
176 }
177 return current
178 }
179 180 func interSlice(slice interface{}) ([]interface{}, bool) {
181 if array, ok := slice.([]interface{}); ok {
182 return array, ok
183 }
184 185 s := reflect.ValueOf(slice)
186 if s.Kind() != reflect.Slice {
187 return nil, false
188 }
189 190 ret := make([]interface{}, s.Len())
191 192 for i := 0; i < s.Len(); i++ {
193 ret[i] = s.Index(i).Interface()
194 }
195 196 return ret, true
197 }
198