diff --git a/util/jsonpath/jsonpath.go b/util/jsonpath/jsonpath.go index 78b6b678..2118688c 100644 --- a/util/jsonpath/jsonpath.go +++ b/util/jsonpath/jsonpath.go @@ -29,12 +29,12 @@ import ( type JSONPath struct { name string parser *Parser - stack [][]reflect.Value // push and pop values in different scopes - cur []reflect.Value // current scope values beginRange int inRange int endRange int + lastEndNode *Node + allowMissingKeys bool } @@ -81,12 +81,12 @@ func (j *JSONPath) FindResults(data interface{}) ([][]reflect.Value, error) { return nil, fmt.Errorf("%s is an incomplete jsonpath template", j.name) } - j.cur = []reflect.Value{reflect.ValueOf(data)} + cur := []reflect.Value{reflect.ValueOf(data)} nodes := j.parser.Root.Nodes fullResult := [][]reflect.Value{} for i := 0; i < len(nodes); i++ { node := nodes[i] - results, err := j.walk(j.cur, node) + results, err := j.walk(cur, node) if err != nil { return nil, err } @@ -94,24 +94,31 @@ func (j *JSONPath) FindResults(data interface{}) ([][]reflect.Value, error) { // encounter an end node, break the current block if j.endRange > 0 && j.endRange <= j.inRange { j.endRange-- + j.lastEndNode = &nodes[i] break } // encounter a range node, start a range loop if j.beginRange > 0 { j.beginRange-- j.inRange++ - for k, value := range results { + for _, value := range results { j.parser.Root.Nodes = nodes[i+1:] - if k == len(results)-1 { - j.inRange-- - } nextResults, err := j.FindResults(value.Interface()) if err != nil { return nil, err } fullResult = append(fullResult, nextResults...) } - break + j.inRange-- + + // Fast forward to resume processing after the most recent end node that was encountered + for k := i + 1; k < len(nodes); k++ { + if &nodes[k] == j.lastEndNode { + i = k + break + } + } + continue } fullResult = append(fullResult, results) } @@ -212,17 +219,11 @@ func (j *JSONPath) evalIdentifier(input []reflect.Value, node *IdentifierNode) ( results := []reflect.Value{} switch node.Name { case "range": - j.stack = append(j.stack, j.cur) j.beginRange++ results = input case "end": - if j.endRange < j.inRange { // inside a loop, break the current block + if j.inRange > 0 { j.endRange++ - break - } - // the loop is about to end, pop value and continue the following execution - if len(j.stack) > 0 { - j.cur, j.stack = j.stack[len(j.stack)-1], j.stack[:len(j.stack)-1] } else { return results, fmt.Errorf("not in range, nothing to end") } diff --git a/util/jsonpath/jsonpath_test.go b/util/jsonpath/jsonpath_test.go index 1513e5e7..7679ccd9 100644 --- a/util/jsonpath/jsonpath_test.go +++ b/util/jsonpath/jsonpath_test.go @@ -298,6 +298,133 @@ func TestKubernetes(t *testing.T) { testJSONPathSortOutput(randomPrintOrderTests, t) } +func TestNestedRanges(t *testing.T) { + var input = []byte(`{ + "items": [ + { + "metadata": { + "name": "pod1" + }, + "spec": { + "containers": [ + { + "name": "foo", + "another": [ + { "name": "value1" }, + { "name": "value2" } + ] + }, + { + "name": "bar", + "another": [ + { "name": "value1" }, + { "name": "value2" } + ] + } + ] + } + }, + { + "metadata": { + "name": "pod2" + }, + "spec": { + "containers": [ + { + "name": "baz", + "another": [ + { "name": "value1" }, + { "name": "value2" } + ] + } + ] + } + } + ] + }`) + var data interface{} + err := json.Unmarshal(input, &data) + if err != nil { + t.Error(err) + } + + testJSONPath( + []jsonpathTest{ + { + "nested range with a trailing newline", + `{range .items[*]}` + + `{.metadata.name}` + + `{":"}` + + `{range @.spec.containers[*]}` + + `{.name}` + + `{","}` + + `{end}` + + `{"+"}` + + `{end}`, + data, + "pod1:foo,bar,+pod2:baz,+", + false, + }, + }, + false, + t, + ) + + testJSONPath( + []jsonpathTest{ + { + "nested range with a trailing character within another nested range with a trailing newline", + `{range .items[*]}` + + `{.metadata.name}` + + `{"~"}` + + `{range @.spec.containers[*]}` + + `{.name}` + + `{":"}` + + `{range @.another[*]}` + + `{.name}` + + `{","}` + + `{end}` + + `{"+"}` + + `{end}` + + `{"#"}` + + `{end}`, + data, + "pod1~foo:value1,value2,+bar:value1,value2,+#pod2~baz:value1,value2,+#", + false, + }, + }, + false, + t, + ) + + testJSONPath( + []jsonpathTest{ + { + "two nested ranges at the same level with a trailing newline", + `{range .items[*]}` + + `{.metadata.name}` + + `{"\t"}` + + `{range @.spec.containers[*]}` + + `{.name}` + + `{" "}` + + `{end}` + + `{"\t"}` + + `{range @.spec.containers[*]}` + + `{.name}` + + `{" "}` + + `{end}` + + `{"\n"}` + + `{end}`, + data, + "pod1\tfoo bar \tfoo bar \npod2\tbaz \tbaz \n", + false, + }, + }, + false, + t, + ) +} + func TestFilterPartialMatchesSometimesMissingAnnotations(t *testing.T) { // for https://issues.k8s.io/45546 var input = []byte(`{