Skip to content

Commit 43a03aa

Browse files
Merge pull request #13 from evilmonkeyinc/chore/expressiontests
Script Engine Tests
2 parents 7533c29 + a6028f0 commit 43a03aa

30 files changed

+2040
-881
lines changed

README.md

Lines changed: 9 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -196,24 +196,15 @@ For maps, the keys will be sorted into alphabetical order and they will be used
196196

197197
For strings instead of returning an array of characters instead will return a substring. For example if you applied `[0:3]` to the string `string` it would return `str`.
198198

199-
## Supported standard evaluation operations
200-
201-
| symbol | name | supported types | example | notes |
202-
| --- | --- | --- | --- | --- |
203-
| == | equals | any | 1 == 1 returns true | |
204-
| != | not equals | any | 1 != 2 returns true | |
205-
| * | multiplication | int\|float | 2*2 returns 4 | |
206-
| / | division | int\|float | 10/5 returns 2 | if you supply two whole numbers you will only get a whole number response, even if there is a remainder i.e. 10/4 would return 2, not 2.5. to include remainders you would need to have the numerator as a float i.e. 10.0/4 would return 2.5 |
207-
| + | addition | int\|float | 2+2 returns 4 | |
208-
| - | subtraction | int\|float | 2-2 returns 0 | |
209-
| % | remainder | int\|float | 5 % 2 returns 1 | this operator will divide the numerator by the denominator and then return the remainder |
210-
| > | greater than | int\|float | 1 > 0 returns true | |
211-
| >= | greater than or equal to | int\|float | 1 >= 1 returns true | |
212-
| < | less than | int\|float | 1 < 2 returns true | |
213-
| <= | less than or equal to | int\|float | 1 <= 1 returns true | |
214-
| && | combine and | expression\|bool | true&&false returns false | evaluate two expressions that return true or false, and return true if both are true |
215-
| \|\| | combine or | expression\|bool | true\|\|false returns true | evaluate two expressions that return true or false, and return true if either are true |
216-
| (...) | sub-expression | expression | (1+2)*3 returns 9 | allows you to isolate a sub-expression so it will be evaluated first separate from the rest of the expression |
199+
## Script Engine
200+
201+
The library supports scripts and filters using a [standard script engine](script/standard/README.md) included with this library.
202+
203+
Additionally, a custom script engine can be created and passed as an additional option when compiling the JSONPath selector
204+
205+
```golang
206+
207+
```
217208

218209
## History
219210

helper_test.go

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
package jsonpath
2+
3+
import (
4+
"github.com/evilmonkeyinc/jsonpath/option"
5+
"github.com/evilmonkeyinc/jsonpath/script"
6+
)
7+
8+
type sampleData struct {
9+
Expensive float64 `json:"expensive"`
10+
Store *storeData `json:"store"`
11+
}
12+
13+
type storeData struct {
14+
Book []*bookData `json:"book"`
15+
}
16+
17+
type bookData struct {
18+
Author string `json:"author"`
19+
Category string `json:"category"`
20+
ISBN string `json:"isbn"`
21+
Price float64 `json:"price"`
22+
Title string `json:"title"`
23+
}
24+
25+
var sampleDataObject *sampleData = &sampleData{
26+
Expensive: 10,
27+
Store: &storeData{
28+
Book: []*bookData{
29+
{
30+
Category: "reference",
31+
Author: "Nigel Rees",
32+
Title: "Sayings of the Century",
33+
Price: 8.95,
34+
},
35+
{
36+
Category: "fiction",
37+
Author: "Evelyn Waugh",
38+
Title: "Sword of Honour",
39+
Price: 12.99,
40+
},
41+
{
42+
Category: "fiction",
43+
Author: "Herman Melville",
44+
Title: "Moby Dick",
45+
ISBN: "0-553-21311-3",
46+
Price: 8.99,
47+
},
48+
{
49+
Category: "fiction",
50+
Author: "J. R. R. Tolkien",
51+
Title: "The Lord of the Rings",
52+
ISBN: "0-395-19395-8",
53+
Price: 22.99,
54+
},
55+
},
56+
},
57+
}
58+
59+
var sampleDataString string = `
60+
{
61+
"store": {
62+
"book": [{
63+
"category": "reference",
64+
"author": "Nigel Rees",
65+
"title": "Sayings of the Century",
66+
"price": 8.95
67+
},
68+
{
69+
"category": "fiction",
70+
"author": "Evelyn Waugh",
71+
"title": "Sword of Honour",
72+
"price": 12.99
73+
},
74+
{
75+
"category": "fiction",
76+
"author": "Herman Melville",
77+
"title": "Moby Dick",
78+
"isbn": "0-553-21311-3",
79+
"price": 8.99
80+
},
81+
{
82+
"category": "fiction",
83+
"author": "J. R. R. Tolkien",
84+
"title": "The Lord of the Rings",
85+
"isbn": "0-395-19395-8",
86+
"price": 22.99
87+
}
88+
],
89+
"bicycle": {
90+
"color": "red",
91+
"price": 19.95
92+
}
93+
},
94+
"expensive": 10
95+
}
96+
`
97+
98+
type testScriptEngine struct {
99+
value interface{}
100+
}
101+
102+
func (engine *testScriptEngine) Compile(expression string, options *option.QueryOptions) (script.CompiledExpression, error) {
103+
return nil, nil
104+
}
105+
106+
func (engine *testScriptEngine) Evaluate(root, current interface{}, expression string, options *option.QueryOptions) (interface{}, error) {
107+
return nil, nil
108+
}

jsonpath.go

Lines changed: 26 additions & 117 deletions
Original file line numberDiff line numberDiff line change
@@ -1,150 +1,59 @@
11
package jsonpath
22

33
import (
4-
"encoding/json"
5-
"fmt"
6-
"strconv"
7-
"strings"
8-
9-
"github.com/evilmonkeyinc/jsonpath/option"
10-
"github.com/evilmonkeyinc/jsonpath/script"
114
"github.com/evilmonkeyinc/jsonpath/script/standard"
125
"github.com/evilmonkeyinc/jsonpath/token"
136
)
147

158
// Compile will compile the JSONPath selector
16-
func Compile(selector string) (*Selector, error) {
17-
engine := new(standard.ScriptEngine)
18-
19-
if selector == "$[?(@.key<3),?(@.key>6)]" {
20-
// TODO
21-
selector = "$[?(@.key<3),?(@.key>6)]"
9+
func Compile(selector string, options ...Option) (*Selector, error) {
10+
jsonPath := &Selector{
11+
selector: selector,
2212
}
2313

24-
jsonPath := &Selector{}
25-
if err := jsonPath.compile(selector, engine); err != nil {
26-
return nil, getInvalidJSONPathSelectorWithReason(selector, err)
14+
for _, option := range options {
15+
if err := option.Apply(jsonPath); err != nil {
16+
return nil, err
17+
}
2718
}
2819

29-
return jsonPath, nil
30-
}
31-
32-
// Query will return the result of the JSONPath selector applied against the specified JSON data.
33-
func Query(selector string, jsonData interface{}) (interface{}, error) {
34-
jsonPath, err := Compile(selector)
35-
if err != nil {
36-
return nil, getInvalidJSONPathSelectorWithReason(selector, err)
20+
// Set defaults if options were not used
21+
if jsonPath.engine == nil {
22+
jsonPath.engine = new(standard.ScriptEngine)
3723
}
38-
return jsonPath.Query(jsonData)
39-
}
40-
41-
// QueryString will return the result of the JSONPath selector applied against the specified JSON data.
42-
func QueryString(selector string, jsonData string) (interface{}, error) {
4324

44-
jsonPath, err := Compile(selector)
25+
tokenStrings, err := token.Tokenize(jsonPath.selector)
4526
if err != nil {
4627
return nil, getInvalidJSONPathSelectorWithReason(selector, err)
4728
}
48-
return jsonPath.QueryString(jsonData)
49-
}
50-
51-
// Selector represents a compiled JSONPath selector
52-
// and exposes functions to query JSON data and objects.
53-
type Selector struct {
54-
Options *option.QueryOptions
55-
engine script.Engine
56-
tokens []token.Token
57-
selector string
58-
}
59-
60-
// String returns the compiled selector string representation
61-
func (query *Selector) String() string {
62-
jsonPath := ""
63-
for _, token := range query.tokens {
64-
jsonPath += fmt.Sprintf("%s", token)
65-
}
66-
return jsonPath
67-
}
68-
69-
func (query *Selector) compile(selector string, engine script.Engine) error {
70-
query.engine = engine
71-
query.selector = selector
72-
73-
tokenStrings, err := token.Tokenize(selector)
74-
if err != nil {
75-
return err
76-
}
7729

7830
tokens := make([]token.Token, len(tokenStrings))
7931
for idx, tokenString := range tokenStrings {
80-
token, err := token.Parse(tokenString, query.engine, query.Options)
32+
token, err := token.Parse(tokenString, jsonPath.engine, jsonPath.Options)
8133
if err != nil {
82-
return err
34+
return nil, getInvalidJSONPathSelectorWithReason(selector, err)
8335
}
8436
tokens[idx] = token
8537
}
86-
query.tokens = tokens
38+
jsonPath.tokens = tokens
8739

88-
return nil
40+
return jsonPath, nil
8941
}
9042

91-
// Query will return the result of the JSONPath query applied against the specified JSON data.
92-
func (query *Selector) Query(root interface{}) (interface{}, error) {
93-
if len(query.tokens) == 0 {
94-
return nil, getInvalidJSONPathSelector(query.selector)
95-
}
96-
97-
tokens := make([]token.Token, 0)
98-
if len(query.tokens) > 1 {
99-
tokens = query.tokens[1:]
100-
}
101-
102-
found, err := query.tokens[0].Apply(root, root, tokens)
43+
// Query will return the result of the JSONPath selector applied against the specified JSON data.
44+
func Query(selector string, jsonData interface{}, options ...Option) (interface{}, error) {
45+
jsonPath, err := Compile(selector, options...)
10346
if err != nil {
104-
return nil, err
47+
return nil, getInvalidJSONPathSelectorWithReason(selector, err)
10548
}
106-
return found, nil
49+
return jsonPath.Query(jsonData)
10750
}
10851

109-
// QueryString will return the result of the JSONPath query applied against the specified JSON data.
110-
func (query *Selector) QueryString(jsonData string) (interface{}, error) {
111-
jsonData = strings.TrimSpace(jsonData)
112-
if jsonData == "" {
113-
return nil, getInvalidJSONData(errDataIsUnexpectedTypeOrNil)
114-
}
115-
116-
var root interface{}
117-
118-
if strings.HasPrefix(jsonData, "{") && strings.HasSuffix(jsonData, "}") {
119-
// object
120-
root = make(map[string]interface{})
121-
if err := json.Unmarshal([]byte(jsonData), &root); err != nil {
122-
return nil, getInvalidJSONData(err)
123-
}
124-
} else if strings.HasPrefix(jsonData, "[") && strings.HasSuffix(jsonData, "]") {
125-
// array
126-
root = make([]interface{}, 0)
127-
if err := json.Unmarshal([]byte(jsonData), &root); err != nil {
128-
return nil, getInvalidJSONData(err)
129-
}
130-
} else if len(jsonData) > 2 && strings.HasPrefix(jsonData, "\"") && strings.HasPrefix(jsonData, "\"") {
131-
// string
132-
root = jsonData[1 : len(jsonData)-1]
133-
} else if strings.ToLower(jsonData) == "true" {
134-
// bool true
135-
root = true
136-
} else if strings.ToLower(jsonData) == "false" {
137-
// bool false
138-
root = false
139-
} else if val, err := strconv.ParseInt(jsonData, 10, 64); err == nil {
140-
// integer
141-
root = val
142-
} else if val, err := strconv.ParseFloat(jsonData, 64); err == nil {
143-
// float
144-
root = val
145-
} else {
146-
return nil, getInvalidJSONData(errDataIsUnexpectedTypeOrNil)
52+
// QueryString will return the result of the JSONPath selector applied against the specified JSON data.
53+
func QueryString(selector string, jsonData string, options ...Option) (interface{}, error) {
54+
jsonPath, err := Compile(selector, options...)
55+
if err != nil {
56+
return nil, getInvalidJSONPathSelectorWithReason(selector, err)
14757
}
148-
149-
return query.Query(root)
58+
return jsonPath.QueryString(jsonData)
15059
}

0 commit comments

Comments
 (0)