accessors.go raw

   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