diff --git a/accessors.go b/accessors.go index 6763162..1377104 100644 --- a/accessors.go +++ b/accessors.go @@ -16,11 +16,18 @@ const ( // arrayAccesRegexString is the regex used to extract the array number // from the access path arrayAccesRegexString = `^(.+)\[([0-9]+)\]$` + + // mapAccessRegexString is the regex used to extract the map key + // from the access path + mapAccessRegexString = `^([^\[]*)\[([^\]]+)\](.*)$` ) // arrayAccesRegex is the compiled arrayAccesRegexString var arrayAccesRegex = regexp.MustCompile(arrayAccesRegexString) +// mapAccessRegex is the compiled mapAccessRegexString +var mapAccessRegex = regexp.MustCompile(mapAccessRegexString) + // Get gets the value using the specified selector and // returns it inside a new Obj object. // @@ -70,13 +77,45 @@ func getIndex(s string) (int, string) { return -1, s } +// getKey returns the key which is held in s by two brackets. +// It also returns the next selector. +func getKey(s string) (string, string) { + selSegs := strings.SplitN(s, PathSeparator, 2) + thisSel := selSegs[0] + nextSel := "" + + if len(selSegs) > 1 { + nextSel = selSegs[1] + } + + mapMatches := mapAccessRegex.FindStringSubmatch(s) + if len(mapMatches) > 0 { + if _, err := strconv.Atoi(mapMatches[2]); err != nil { + thisSel = mapMatches[1] + nextSel = "[" + mapMatches[2] + "]" + mapMatches[3] + + if thisSel == "" { + thisSel = mapMatches[2] + nextSel = mapMatches[3] + } + + if nextSel == "" { + selSegs = []string{"", ""} + } else if nextSel[0] == '.' { + nextSel = nextSel[1:] + } + } + } + + return thisSel, nextSel +} + // access accesses the object using the selector and performs the // appropriate action. func access(current interface{}, selector string, value interface{}, isSet bool) interface{} { - selSegs := strings.SplitN(selector, PathSeparator, 2) - thisSel := selSegs[0] - index := -1 + thisSel, nextSel := getKey(selector) + index := -1 if strings.Contains(thisSel, "[") { index, thisSel = getIndex(thisSel) } @@ -88,7 +127,7 @@ func access(current interface{}, selector string, value interface{}, isSet bool) switch current.(type) { case map[string]interface{}: curMSI := current.(map[string]interface{}) - if len(selSegs) <= 1 && isSet { + if nextSel == "" && isSet { curMSI[thisSel] = value return nil } @@ -112,8 +151,8 @@ func access(current interface{}, selector string, value interface{}, isSet bool) } } } - if len(selSegs) > 1 { - current = access(current, selSegs[1], value, isSet) + if nextSel != "" { + current = access(current, nextSel, value, isSet) } return current } diff --git a/accessors_test.go b/accessors_test.go index c8f9ec0..8bdb69b 100644 --- a/accessors_test.go +++ b/accessors_test.go @@ -43,6 +43,25 @@ func TestAccessorsAccessGetDeepDeep(t *testing.T) { } assert.Equal(t, 4, m.Get("one.two.three.four").Data()) + assert.Equal(t, 4, m.Get("one[two][three][four]").Data()) +} + +func TestAccessorsGetWithComplexKey(t *testing.T) { + m := objx.Map{ + "domains": objx.Map{ + "example-dot-com": objx.Map{ + "apex": "example", + }, + "example.com": objx.Map{ + "apex": "example", + }, + }, + } + + assert.Equal(t, "example", m.Get("domains.example-dot-com.apex").Data()) + + assert.Equal(t, "example", m.Get("domains[example.com].apex").Data()) + assert.Equal(t, "example", m.Get("domains[example.com][apex]").Data()) } func TestAccessorsAccessGetInsideArray(t *testing.T) {