github.com/tickoalcantara12/micro/v3@v3.0.0-20221007104245-9d75b9bcbab9/util/qson/qson.go (about) 1 // Licensed under the Apache License, Version 2.0 (the "License"); 2 // you may not use this file except in compliance with the License. 3 // You may obtain a copy of the License at 4 // 5 // https://www.apache.org/licenses/LICENSE-2.0 6 // 7 // Unless required by applicable law or agreed to in writing, software 8 // distributed under the License is distributed on an "AS IS" BASIS, 9 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 // See the License for the specific language governing permissions and 11 // limitations under the License. 12 // 13 // Original source: github.com/micro/go-micro/v3/util/qson/qson_test.go 14 15 // Package qson implmenets decoding of URL query params 16 // into JSON and Go values (using JSON struct tags). 17 // 18 // See https://golang.org/pkg/encoding/json/ for more 19 // details on JSON struct tags. 20 package qson 21 22 import ( 23 "encoding/json" 24 "errors" 25 "net/url" 26 "regexp" 27 "strings" 28 ) 29 30 var ( 31 // ErrInvalidParam is returned when invalid data is provided to the ToJSON or Unmarshal function. 32 // Specifically, this will be returned when there is no equals sign present in the URL query parameter. 33 ErrInvalidParam error = errors.New("qson: invalid url query param provided") 34 35 bracketSplitter *regexp.Regexp 36 ) 37 38 func init() { 39 bracketSplitter = regexp.MustCompile("\\[|\\]") 40 } 41 42 func btSplitter(str string) []string { 43 r := bracketSplitter.Split(str, -1) 44 for idx, s := range r { 45 if len(s) == 0 { 46 if len(r) > idx+1 { 47 copy(r[idx:], r[idx+1:]) 48 r = r[:len(r)-1] 49 } 50 } 51 } 52 return r 53 } 54 55 // Unmarshal will take a dest along with URL 56 // query params and attempt to first turn the query params 57 // into JSON and then unmarshal those into the dest variable 58 // 59 // BUG(joncalhoun): If a URL query param value is something 60 // like 123 but is expected to be parsed into a string this 61 // will currently result in an error because the JSON 62 // transformation will assume this is intended to be an int. 63 // This should only affect the Unmarshal function and 64 // could likely be fixed, but someone will need to submit a 65 // PR if they want that fixed. 66 func Unmarshal(dst interface{}, query string) error { 67 b, err := ToJSON(query) 68 if err != nil { 69 return err 70 } 71 return json.Unmarshal(b, dst) 72 } 73 74 // ToJSON will turn a query string like: 75 // cat=1&bar%5Bone%5D%5Btwo%5D=2&bar[one][red]=112 76 // Into a JSON object with all the data merged as nicely as 77 // possible. Eg the example above would output: 78 // {"bar":{"one":{"two":2,"red":112}}} 79 func ToJSON(query string) ([]byte, error) { 80 var ( 81 builder interface{} = make(map[string]interface{}) 82 ) 83 params := strings.Split(query, "&") 84 for _, part := range params { 85 tempMap, err := queryToMap(part) 86 if err != nil { 87 return nil, err 88 } 89 builder = merge(builder, tempMap) 90 } 91 return json.Marshal(builder) 92 } 93 94 // queryToMap turns something like a[b][c]=4 into 95 // map[string]interface{}{ 96 // "a": map[string]interface{}{ 97 // "b": map[string]interface{}{ 98 // "c": 4, 99 // }, 100 // }, 101 // } 102 func queryToMap(param string) (map[string]interface{}, error) { 103 rawKey, rawValue, err := splitKeyAndValue(param) 104 if err != nil { 105 return nil, err 106 } 107 rawValue, err = url.QueryUnescape(rawValue) 108 if err != nil { 109 return nil, err 110 } 111 rawKey, err = url.QueryUnescape(rawKey) 112 if err != nil { 113 return nil, err 114 } 115 116 pieces := btSplitter(rawKey) 117 key := pieces[0] 118 119 // If len==1 then rawKey has no [] chars and we can just 120 // decode this as key=value into {key: value} 121 if len(pieces) == 1 { 122 var value interface{} 123 // First we try parsing it as an int, bool, null, etc 124 err = json.Unmarshal([]byte(rawValue), &value) 125 if err != nil { 126 // If we got an error we try wrapping the value in 127 // quotes and processing it as a string 128 err = json.Unmarshal([]byte("\""+rawValue+"\""), &value) 129 if err != nil { 130 // If we can't decode as a string we return the err 131 return nil, err 132 } 133 } 134 return map[string]interface{}{ 135 key: value, 136 }, nil 137 } 138 139 // If len > 1 then we have something like a[b][c]=2 140 // so we need to turn this into {"a": {"b": {"c": 2}}} 141 // To do this we break our key into two pieces: 142 // a and b[c] 143 // and then we set {"a": queryToMap("b[c]", value)} 144 ret := make(map[string]interface{}, 0) 145 ret[key], err = queryToMap(buildNewKey(rawKey) + "=" + rawValue) 146 if err != nil { 147 return nil, err 148 } 149 150 // When URL params have a set of empty brackets (eg a[]=1) 151 // it is assumed to be an array. This will get us the 152 // correct value for the array item and return it as an 153 // []interface{} so that it can be merged properly. 154 if pieces[1] == "" { 155 temp := ret[key].(map[string]interface{}) 156 ret[key] = []interface{}{temp[""]} 157 } 158 return ret, nil 159 } 160 161 // buildNewKey will take something like: 162 // origKey = "bar[one][two]" 163 // pieces = [bar one two ] 164 // and return "one[two]" 165 func buildNewKey(origKey string) string { 166 pieces := btSplitter(origKey) 167 168 ret := origKey[len(pieces[0])+1:] 169 ret = ret[:len(pieces[1])] + ret[len(pieces[1])+1:] 170 return ret 171 } 172 173 // splitKeyAndValue splits a URL param at the last equal 174 // sign and returns the two strings. If no equal sign is 175 // found, the ErrInvalidParam error is returned. 176 func splitKeyAndValue(param string) (string, string, error) { 177 li := strings.LastIndex(param, "=") 178 if li == -1 { 179 return "", "", ErrInvalidParam 180 } 181 return param[:li], param[li+1:], nil 182 } 183 184 // merge merges a with b if they are either both slices 185 // or map[string]interface{} types. Otherwise it returns b. 186 func merge(a interface{}, b interface{}) interface{} { 187 switch aT := a.(type) { 188 case map[string]interface{}: 189 return mergeMap(aT, b.(map[string]interface{})) 190 case []interface{}: 191 return mergeSlice(aT, b.([]interface{})) 192 default: 193 return b 194 } 195 } 196 197 // mergeMap merges a with b, attempting to merge any nested 198 // values in nested maps but eventually overwriting anything 199 // in a that can't be merged with whatever is in b. 200 func mergeMap(a map[string]interface{}, b map[string]interface{}) map[string]interface{} { 201 for bK, bV := range b { 202 if _, ok := a[bK]; ok { 203 a[bK] = merge(a[bK], bV) 204 } else { 205 a[bK] = bV 206 } 207 } 208 return a 209 } 210 211 // mergeSlice merges a with b and returns the result. 212 func mergeSlice(a []interface{}, b []interface{}) []interface{} { 213 a = append(a, b...) 214 return a 215 }