Skip to content

Commit 74f8e22

Browse files
committed
Improved performance of recursive descent search, and other minor improvements
1 parent 5e6a366 commit 74f8e22

6 files changed

+91
-167
lines changed

jsonpath_parser.go

+15-6
Original file line numberDiff line numberDiff line change
@@ -238,13 +238,25 @@ func (p *jsonPathParser) pushChildWildcardIdentifier() {
238238
}
239239

240240
func (p *jsonPathParser) pushRecursiveChildIdentifier(node syntaxNode) {
241+
var nextMapRequired, nextListRequired bool
242+
switch node.(type) {
243+
case *syntaxChildWildcardIdentifier, *syntaxChildMultiIdentifier, *syntaxFilterQualifier:
244+
nextMapRequired = true
245+
nextListRequired = true
246+
case *syntaxChildSingleIdentifier:
247+
nextMapRequired = true
248+
case *syntaxUnionQualifier:
249+
nextListRequired = true
250+
}
241251
p.push(&syntaxRecursiveChildIdentifier{
242252
syntaxBasicNode: &syntaxBasicNode{
243253
text: `..`,
244254
valueGroup: true,
245255
next: node,
246256
accessorMode: p.accessorMode,
247257
},
258+
nextMapRequired: nextMapRequired,
259+
nextListRequired: nextListRequired,
248260
})
249261
}
250262

@@ -269,12 +281,9 @@ func (p *jsonPathParser) pushFilterQualifier(query syntaxQuery) {
269281
}
270282

271283
func (p *jsonPathParser) pushScriptQualifier(text string) {
272-
p.push(&syntaxScriptQualifier{
273-
syntaxBasicNode: &syntaxBasicNode{
274-
valueGroup: true,
275-
accessorMode: p.accessorMode,
276-
},
277-
command: text,
284+
panic(ErrorNotSupported{
285+
feature: `script`,
286+
path: `[(` + text + `)]`,
278287
})
279288
}
280289

syntax_node_identifier_child_wildcard.go

+3-1
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,9 @@ func (i *syntaxChildWildcardIdentifier) retrieveMap(
4646
keys[index] = key
4747
index++
4848
}
49-
keys.Sort()
49+
if len(keys) > 1 {
50+
keys.Sort()
51+
}
5052
for _, key := range keys {
5153
if err := i.retrieveMapNext(root, srcMap, key, result); err != nil {
5254
childErrorMap[err] = struct{}{}

syntax_node_identifier_recursive_child.go

+33-19
Original file line numberDiff line numberDiff line change
@@ -4,30 +4,44 @@ import "sort"
44

55
type syntaxRecursiveChildIdentifier struct {
66
*syntaxBasicNode
7+
8+
nextMapRequired bool
9+
nextListRequired bool
710
}
811

912
func (i *syntaxRecursiveChildIdentifier) retrieve(
1013
root, current interface{}, result *[]interface{}) error {
1114

12-
switch typedNodes := current.(type) {
13-
case map[string]interface{}:
14-
i.retrieveAnyValueNext(root, typedNodes, result)
15-
16-
index, keys := 0, make(sort.StringSlice, len(typedNodes))
17-
for key := range typedNodes {
18-
keys[index] = key
19-
index++
20-
}
21-
keys.Sort()
22-
for _, key := range keys {
23-
i.retrieve(root, typedNodes[key], result)
24-
}
25-
26-
case []interface{}:
27-
i.retrieveAnyValueNext(root, typedNodes, result)
28-
29-
for _, node := range typedNodes {
30-
i.retrieve(root, node, result)
15+
targetNodes := make([]interface{}, 1, 5)
16+
targetNodes[0] = current
17+
18+
keys := make(sort.StringSlice, 0, 2)
19+
20+
for len(targetNodes) > 0 {
21+
currentNode := targetNodes[len(targetNodes)-1]
22+
targetNodes = targetNodes[:len(targetNodes)-1]
23+
switch typedNodes := currentNode.(type) {
24+
case map[string]interface{}:
25+
if i.nextMapRequired {
26+
i.retrieveAnyValueNext(root, typedNodes, result)
27+
}
28+
keys = keys[:0]
29+
for key := range typedNodes {
30+
keys = append(keys, key)
31+
}
32+
if len(keys) > 1 {
33+
keys.Sort()
34+
}
35+
for index := len(typedNodes) - 1; index >= 0; index-- {
36+
targetNodes = append(targetNodes, typedNodes[keys[index]])
37+
}
38+
case []interface{}:
39+
if i.nextListRequired {
40+
i.retrieveAnyValueNext(root, typedNodes, result)
41+
}
42+
for index := len(typedNodes) - 1; index >= 0; index-- {
43+
targetNodes = append(targetNodes, typedNodes[index])
44+
}
3145
}
3246
}
3347

syntax_node_qualifier_filter.go

+3-1
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,9 @@ func (f *syntaxFilterQualifier) retrieveMap(
4949
keys[index] = key
5050
index++
5151
}
52-
keys.Sort()
52+
if len(keys) > 1 {
53+
keys.Sort()
54+
}
5355
valueList := make([]interface{}, len(keys))
5456
for index := range keys {
5557
valueList[index] = srcMap[keys[index]]

syntax_node_qualifier_script.go

-14
This file was deleted.

test_jsonpath_benchmark_test.go

+37-126
Original file line numberDiff line numberDiff line change
@@ -41,132 +41,6 @@ func execParserFunc(jsonPath, srcJSON string, b *testing.B) {
4141
}
4242
}
4343

44-
// =====================================================================
45-
// Retrieve
46-
47-
func BenchmarkRetrieve_dotNotation(b *testing.B) {
48-
jsonPath := `$.a`
49-
srcJSON := `{"a":123.456}`
50-
execRetrieve(jsonPath, srcJSON, b)
51-
}
52-
53-
func BenchmarkRetrieve_bracketNotation(b *testing.B) {
54-
jsonPath := `$['a']`
55-
srcJSON := `{"a":123.456}`
56-
execRetrieve(jsonPath, srcJSON, b)
57-
}
58-
59-
func BenchmarkRetrieve_wildcard_identifier_dotNotation(b *testing.B) {
60-
jsonPath := `$.*`
61-
srcJSON := `{"a":123.456}`
62-
execRetrieve(jsonPath, srcJSON, b)
63-
}
64-
65-
func BenchmarkRetrieve_wildcard_identifier_bracketNotation(b *testing.B) {
66-
jsonPath := `$[*]`
67-
srcJSON := `{"a":123.456}`
68-
execRetrieve(jsonPath, srcJSON, b)
69-
}
70-
71-
func BenchmarkRetrieve_multi_identifier(b *testing.B) {
72-
jsonPath := `$['a','a']`
73-
srcJSON := `{"a":123.456}`
74-
execRetrieve(jsonPath, srcJSON, b)
75-
}
76-
77-
func BenchmarkRetrieve_qualifier_index(b *testing.B) {
78-
jsonPath := `$[0]`
79-
srcJSON := `[{"a":123.456}]`
80-
execRetrieve(jsonPath, srcJSON, b)
81-
}
82-
83-
func BenchmarkRetrieve_qualifier_slice(b *testing.B) {
84-
jsonPath := `$[0:1]`
85-
srcJSON := `[{"a":123.456}]`
86-
execRetrieve(jsonPath, srcJSON, b)
87-
}
88-
89-
func BenchmarkRetrieve_qualifier_wildcard(b *testing.B) {
90-
jsonPath := `$[*]`
91-
srcJSON := `[{"a":123.456}]`
92-
execRetrieve(jsonPath, srcJSON, b)
93-
}
94-
95-
func BenchmarkRetrieve_qualifier_union(b *testing.B) {
96-
jsonPath := `$[0,0]`
97-
srcJSON := `[{"a":123.456}]`
98-
execRetrieve(jsonPath, srcJSON, b)
99-
}
100-
101-
func BenchmarkRetrieve_filter_logicalOR(b *testing.B) {
102-
jsonPath := `$[?(@||@)]`
103-
srcJSON := `[{"a":1}]`
104-
execRetrieve(jsonPath, srcJSON, b)
105-
}
106-
107-
func BenchmarkRetrieve_filter_logicalAND(b *testing.B) {
108-
jsonPath := `$[?(@&&@)]`
109-
srcJSON := `[{"a":1}]`
110-
execRetrieve(jsonPath, srcJSON, b)
111-
}
112-
113-
func BenchmarkRetrieve_filter_nodeFilter(b *testing.B) {
114-
jsonPath := `$[?(@.a)]`
115-
srcJSON := `[{"a":1}]`
116-
execRetrieve(jsonPath, srcJSON, b)
117-
}
118-
119-
func BenchmarkRetrieve_filter_logicalNOT(b *testing.B) {
120-
jsonPath := `$[?(!@.a)]`
121-
srcJSON := `[{"a":1},{"b":1}]`
122-
execRetrieve(jsonPath, srcJSON, b)
123-
}
124-
125-
func BenchmarkRetrieve_filter_compareEQ(b *testing.B) {
126-
jsonPath := `$[?(@.a==1)]`
127-
srcJSON := `[{"a":1}]`
128-
execRetrieve(jsonPath, srcJSON, b)
129-
}
130-
131-
func BenchmarkRetrieve_filter_compareNE(b *testing.B) {
132-
jsonPath := `$[?(@.a!=2)]`
133-
srcJSON := `[{"a":1}]`
134-
execRetrieve(jsonPath, srcJSON, b)
135-
}
136-
137-
func BenchmarkRetrieve_filter_compareGE(b *testing.B) {
138-
jsonPath := `$[?(@.a<=2)]`
139-
srcJSON := `[{"a":1}]`
140-
execRetrieve(jsonPath, srcJSON, b)
141-
}
142-
143-
func BenchmarkRetrieve_filter_compareGT(b *testing.B) {
144-
jsonPath := `$[?(@.a<2)]`
145-
srcJSON := `[{"a":1}]`
146-
execRetrieve(jsonPath, srcJSON, b)
147-
}
148-
149-
func BenchmarkRetrieve_filter_compareLE(b *testing.B) {
150-
jsonPath := `$[?(@.a>=0)]`
151-
srcJSON := `[{"a":1}]`
152-
execRetrieve(jsonPath, srcJSON, b)
153-
}
154-
155-
func BenchmarkRetrieve_filter_compareLT(b *testing.B) {
156-
jsonPath := `$[?(@.a>0)]`
157-
srcJSON := `[{"a":1}]`
158-
execRetrieve(jsonPath, srcJSON, b)
159-
}
160-
161-
func BenchmarkRetrieve_filter_regex(b *testing.B) {
162-
jsonPath := `$[?(@.a =~ /ab/)]`
163-
srcJSON := `[{"a":"abc"}]`
164-
execRetrieve(jsonPath, srcJSON, b)
165-
}
166-
167-
// =====================================================================
168-
// ParserFunc
169-
17044
func BenchmarkParserFunc_dotNotation(b *testing.B) {
17145
jsonPath := `$.a`
17246
srcJSON := `{"a":123.456}`
@@ -286,3 +160,40 @@ func BenchmarkParserFunc_filter_regex(b *testing.B) {
286160
srcJSON := `[{"a":"abc"}]`
287161
execParserFunc(jsonPath, srcJSON, b)
288162
}
163+
164+
func BenchmarkParserFunc_recursive(b *testing.B) {
165+
jsonPath := `$..price`
166+
srcJSON := `{ "store": {
167+
"book": [
168+
{ "category": "reference",
169+
"author": "Nigel Rees",
170+
"title": "Sayings of the Century",
171+
"price": 8.95
172+
},
173+
{ "category": "fiction",
174+
"author": "Evelyn Waugh",
175+
"title": "Sword of Honour",
176+
"price": 12.99
177+
},
178+
{ "category": "fiction",
179+
"author": "Herman Melville",
180+
"title": "Moby Dick",
181+
"isbn": "0-553-21311-3",
182+
"price": 8.99
183+
},
184+
{ "category": "fiction",
185+
"author": "J. R. R. Tolkien",
186+
"title": "The Lord of the Rings",
187+
"isbn": "0-395-19395-8",
188+
"price": 22.99
189+
}
190+
],
191+
"bicycle": {
192+
"color": "red",
193+
"price": 19.95
194+
}
195+
}
196+
}`
197+
198+
execParserFunc(jsonPath, srcJSON, b)
199+
}

0 commit comments

Comments
 (0)