...

Source file src/github.com/stretchr/objx/accessors.go

Documentation: github.com/stretchr/objx

     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  

View as plain text