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  }