Skip to content

Commit 2ca1312

Browse files
author
Boris Zhguchev
committed
gramma
1 parent 81a1ec1 commit 2ca1312

File tree

11 files changed

+154
-110
lines changed

11 files changed

+154
-110
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,6 @@
1515
* add logical OR and logical And to filters
1616
* fix bugs with objects in filters
1717
* add internal macros to generate path objects
18+
* **`1.0.0`**
19+
* add functions (size)
20+
* change a logical operator `size` into function `size()`

README.md

Lines changed: 67 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -37,20 +37,35 @@ that ```$.shop.orders[?(@.active)].id``` and get the result ``` [1,4] ```
3737

3838
### The jsonpath description
3939

40+
#### Functions
41+
42+
##### Size
43+
A function `size()` transforms the output of the filtered expression into a size of this element
44+
45+
| Json element | function size |
46+
|--------------|--------------------------|
47+
| null | 0 |
48+
| numbers | 1 |
49+
| string | a length of given string |
50+
| array | a length of given array |
51+
| objects | a length of set of keys |
52+
53+
`$.some_field.size()`
54+
4055
#### Operators
4156

42-
| Operator | Description | Where to use |
43-
| --- | --- | --- |
44-
| `$` | Pointer to the root of the json. | It is gently advising to start every jsonpath from the root. Also, inside the filters to point out that the path is starting from the root.
45-
| `@` | Pointer to the current element inside the filter operations. | It is used inside the filter operations to iterate the collection.
46-
| `*` or `[*]` | Wildcard. It brings to the list all objects and elements regardless their names. | It is analogue a flatmap operation.
47-
| `<..>`| Descent operation. It brings to the list all objects, children of that objects and etc | It is analogue a flatmap operation.
48-
| `.<name>` or `.['<name>']` | the key pointing to the field of the object | It is used to obtain the specific field.
49-
| `['<name>' (, '<name>')]` | the list of keys | the same usage as for a single key but for list
50-
| `[<number>]` | the filter getting the element by its index. |
51-
| `[<number> (, <number>)]` | the list if elements of array according to their indexes representing these numbers. |
52-
| `[<start>:<end>:<step>]` | slice operator to get a list of element operating with their indexes. By default step = 1, start = 0, end = array len. The elements can be omitted ```[:]```
53-
| `[?(<expression>)]` | the logical expression to filter elements in the list. | It is used with arrays preliminary.
57+
| Operator | Description | Where to use |
58+
|----------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------|
59+
| `$` | Pointer to the root of the json. | It is gently advising to start every jsonpath from the root. Also, inside the filters to point out that the path is starting from the root. |
60+
| `@` | Pointer to the current element inside the filter operations. | It is used inside the filter operations to iterate the collection. |
61+
| `*` or `[*]` | Wildcard. It brings to the list all objects and elements regardless their names. | It is analogue a flatmap operation. |
62+
| `<..>` | Descent operation. It brings to the list all objects, children of that objects and etc | It is analogue a flatmap operation. |
63+
| `.<name>` or `.['<name>']` | the key pointing to the field of the object | It is used to obtain the specific field. |
64+
| `['<name>' (, '<name>')]` | the list of keys | the same usage as for a single key but for list |
65+
| `[<number>]` | the filter getting the element by its index. | |
66+
| `[<number> (, <number>)]` | the list if elements of array according to their indexes representing these numbers. | |
67+
| `[<start>:<end>:<step>]` | slice operator to get a list of element operating with their indexes. By default step = 1, start = 0, end = array len. The elements can be omitted ```[:]``` | |
68+
| `[?(<expression>)]` | the logical expression to filter elements in the list. | It is used with arrays preliminary. |
5469

5570
#### Filter expressions
5671

@@ -61,22 +76,21 @@ following elements:
6176
string value `'value'`, array of them or another json path instance.
6277
- Expression sign, denoting what action can be performed
6378

64-
| Expression sign | Description | Where to use |
65-
| --- | --- | --- |
66-
| `==`| Equal | To compare numbers or string literals
67-
| `!=`| Unequal| To compare numbers or string literals in opposite way to equals
68-
| `<` | Less | To compare numbers
69-
| `>` | Greater | To compare numbers
70-
| `<=`| Less or equal | To compare numbers
71-
| `>=`| Greater or equal | To compare numbers
72-
| `~=`| Regular expression | To find the incoming right side in the left side.
73-
| `in`| Find left element in the list of right elements. |
74-
| `nin`| The same one as saying above but carrying the opposite sense. |
75-
| `size`| The size of array on the left size should be corresponded to the number on the right side. |
76-
| `noneOf`| The left size has no intersection with right |
77-
| `anyOf` | The left size has at least one intersection with right |
78-
| `subsetOf` | The left is a subset of the right side
79-
| | Exists operator. | The operator checks the existens of the field depicted on the left side like that `[?(@.key.isActive)]`
79+
| Expression sign | Description | Where to use |
80+
|-----------------|---------------------------------------------------------------|---------------------------------------------------------------------------------------------------------|
81+
| `==` | Equal | To compare numbers or string literals |
82+
| `!=` | Unequal | To compare numbers or string literals in opposite way to equals |
83+
| `<` | Less | To compare numbers |
84+
| `>` | Greater | To compare numbers |
85+
| `<=` | Less or equal | To compare numbers |
86+
| `>=` | Greater or equal | To compare numbers |
87+
| `~=` | Regular expression | To find the incoming right side in the left side. |
88+
| `in` | Find left element in the list of right elements. | |
89+
| `nin` | The same one as saying above but carrying the opposite sense. | |
90+
| `noneOf` | The left side has no intersection with right | |
91+
| `anyOf` | The left side has at least one intersection with right | |
92+
| `subsetOf` | The left is a subset of the right side | |
93+
| | Exists operator. | The operator checks the existens of the field depicted on the left side like that `[?(@.key.isActive)]` |
8094

8195
Filter expressions can be chained using `||` and `&&` (logical or and logical and correspondingly) in the following way:
8296
```json
@@ -149,24 +163,24 @@ Given the json
149163
}
150164
```
151165

152-
| JsonPath | Result |
153-
| :------- | :----- |
154-
| `$.store.book[*].author`| The authors of all books |
155-
| `$..book[?(@.isbn)]` | All books with an ISBN number |
156-
| `$.store.*` | All things, both books and bicycles |
157-
| `$..author` | All authors |
158-
| `$.store..price` | The price of everything |
159-
| `$..book[2]` | The third book |
160-
| `$..book[-2]` | The second to last book |
161-
| `$..book[0,1]` | The first two books |
162-
| `$..book[:2]` | All books from index 0 (inclusive) until index 2 (exclusive) |
163-
| `$..book[1:2]` | All books from index 1 (inclusive) until index 2 (exclusive) |
164-
| `$..book[-2:]` | Last two books |
165-
| `$..book[2:]` | Book number two from tail |
166-
| `$.store.book[?(@.price < 10)]` | All books in store cheaper than 10 |
167-
| `$..book[?(@.price <= $.expensive)]` | All books in store that are not "expensive" |
168-
| `$..book[?(@.author ~= /.*REES/i)]` | All books matching regex (ignore case) |
169-
| `$..*` | Give me every thing
166+
| JsonPath | Result |
167+
|:-------------------------------------|:-------------------------------------------------------------|
168+
| `$.store.book[*].author` | The authors of all books |
169+
| `$..book[?(@.isbn)]` | All books with an ISBN number |
170+
| `$.store.*` | All things, both books and bicycles |
171+
| `$..author` | All authors |
172+
| `$.store..price` | The price of everything |
173+
| `$..book[2]` | The third book |
174+
| `$..book[-2]` | The second to last book |
175+
| `$..book[0,1]` | The first two books |
176+
| `$..book[:2]` | All books from index 0 (inclusive) until index 2 (exclusive) |
177+
| `$..book[1:2]` | All books from index 1 (inclusive) until index 2 (exclusive) |
178+
| `$..book[-2:]` | Last two books |
179+
| `$..book[2:]` | Book number two from tail |
180+
| `$.store.book[?(@.price < 10)]` | All books in store cheaper than 10 |
181+
| `$..book[?(@.price <= $.expensive)]` | All books in store that are not "expensive" |
182+
| `$..book[?(@.author ~= /.*REES/i)]` | All books matching regex (ignore case) |
183+
| `$..*` | Give me every thing |
170184

171185
### The library
172186

@@ -195,16 +209,16 @@ or with a separate instantiation:
195209
use std::str::FromStr;
196210
fn test(){
197211
let json: Value = serde_json::from_str("{}").unwrap();
198-
let v = json.path("$..book[?(@.author size 10)].title").unwrap();
212+
let v = json.path("$..book[?(@.author.size() == 10)].title").unwrap();
199213
assert_eq!(v, json!([]));
200214

201215
let json: Value = serde_json::from_str("{}").unwrap();
202-
let path = &json.path("$..book[?(@.author size 10)].title").unwrap();
216+
let path = &json.path("$..book[?(@.author.size() == 10)].title").unwrap();
203217

204218
assert_eq!(path, &json!(["Sayings of the Century"]));
205219

206220
let json: Box<Value> = serde_json::from_str("{}").unwrap();
207-
let path: Box<JsonPathInst> = Box::from(JsonPathInst::from_str("$..book[?(@.author size 10)].title").unwrap());
221+
let path: Box<JsonPathInst> = Box::from(JsonPathInst::from_str("$..book[?(@.author.size() == 10)].title").unwrap());
208222
let finder = JsonPathFinder::new(json, path);
209223

210224
let v = finder.find_slice();
@@ -235,11 +249,11 @@ use jsonpath_rust::JsonPathQuery;
235249

236250
fn test(){
237251
let json: Value = serde_json::from_str("{}").unwrap();
238-
let v = json.path("$..book[?(@.author size 10)].title").unwrap();
252+
let v = json.path("$..book[?(@.author.size() == 10)].title").unwrap();
239253
assert_eq!(v, json!([]));
240254

241255
let json: Value = serde_json::from_str(template_json()).unwrap();
242-
let path = &json.path("$..book[?(@.author size 10)].title").unwrap();
256+
let path = &json.path("$..book[?(@.author.size() == 10)].title").unwrap();
243257

244258
assert_eq!(path, &json!(["Sayings of the Century"]));
245259
}
@@ -262,6 +276,8 @@ pub enum JsonPath {
262276
Current(Box<JsonPath>),
263277
// <- @
264278
Wildcard,
279+
280+
Fn(Function),
265281
// <- *
266282
Empty, // the structure to avoid inconsistency
267283
}

src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -547,7 +547,7 @@ mod tests {
547547
#[test]
548548
fn find_slice_test() {
549549
let json: Box<Value> = serde_json::from_str(template_json()).expect("to get json");
550-
let path: Box<JsonPathInst> = Box::from(JsonPathInst::from_str("$..book[?(@.author size 10)].title")
550+
let path: Box<JsonPathInst> = Box::from(JsonPathInst::from_str("$..book[?(@.author.size() == 10)].title")
551551
.expect("the path is correct"));
552552
let finder = JsonPathFinder::new(json, path);
553553

src/parser/grammar/json_path.pest

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,17 @@ char = _{
1515
| "\\" ~ ("u" ~ ASCII_HEX_DIGIT{4})
1616
}
1717
root = {"$"}
18-
sign = { "==" | "!=" | "~=" | ">=" | ">" | "<=" | "<" | "in" | "nin" | "size" | "noneOf" | "anyOf" | "subsetOf"}
19-
key_lim = {(word | ASCII_DIGIT | specs)+}
18+
19+
sign = { "==" | "!=" | "~=" | ">=" | ">" | "<=" | "<" | "in" | "nin" | "noneOf" | "anyOf" | "subsetOf"}
20+
key_lim = {!"size()" ~ (word | ASCII_DIGIT | specs)+}
2021
key_unlim = {"[" ~ string_qt ~ "]"}
2122
key = ${key_lim | key_unlim}
2223

2324
descent = {dot ~ dot ~ key}
2425
wildcard = {dot? ~ "[" ~"*"~"]" | dot ~ "*"}
2526
current = {"@" ~ chain?}
2627
field = ${dot? ~ key_unlim | dot ~ key_lim }
27-
28+
function = { dot ~ "size" ~ "(" ~ ")"}
2829
unsigned = {("0" | ASCII_NONZERO_DIGIT ~ ASCII_DIGIT*)}
2930
signed = {min? ~ unsigned}
3031
start_slice = {signed}
@@ -44,6 +45,8 @@ atom = {chain | string_qt | number }
4445

4546
index = {dot? ~ "["~ (unit_keys | unit_indexes | slice | unsigned |filter ) ~ "]" }
4647

47-
chain = {(root | descent | wildcard | current | field | index)+}
48+
49+
50+
chain = {(root | descent | wildcard | current | field | index | function)+}
4851

4952
path = {SOI ~ chain ~ EOI }

src/parser/macros.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,11 @@ macro_rules! chain {
4949
}};
5050
}
5151

52+
#[macro_export]
53+
macro_rules! function {
54+
(size) => {JsonPath::Fn(Function::Size)}
55+
}
56+
5257
#[macro_export]
5358
macro_rules! path {
5459
( ) => {JsonPath::Empty};

src/parser/model.rs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ pub enum JsonPath {
1919
Current(Box<JsonPath>),
2020
/// The * operator
2121
Wildcard,
22+
/// Functions that can calculate some expressions
23+
Fn(Function),
2224
/// The item uses to define the unresolved state
2325
Empty,
2426
}
@@ -29,6 +31,11 @@ impl JsonPath {
2931
parse_json_path(v).map_err(|e| e.to_string())
3032
}
3133
}
34+
#[derive(Debug,PartialEq, Clone)]
35+
pub enum Function {
36+
/// size()
37+
Size,
38+
}
3239

3340
#[derive(Debug, Clone)]
3441
pub enum JsonPathIndex {
@@ -88,7 +95,6 @@ pub enum FilterSign {
8895
Regex,
8996
In,
9097
Nin,
91-
Size,
9298
NoneOf,
9399
AnyOf,
94100
SubSetOf,
@@ -107,7 +113,6 @@ impl FilterSign {
107113
"~=" => FilterSign::Regex,
108114
"in" => FilterSign::In,
109115
"nin" => FilterSign::Nin,
110-
"size" => FilterSign::Size,
111116
"noneOf" => FilterSign::NoneOf,
112117
"anyOf" => FilterSign::AnyOf,
113118
"subsetOf" => FilterSign::SubSetOf,
@@ -127,6 +132,7 @@ impl PartialEq for JsonPath {
127132
(JsonPath::Current(jp1), JsonPath::Current(jp2)) => jp1 == jp2,
128133
(JsonPath::Chain(ch1), JsonPath::Chain(ch2)) => ch1 == ch2,
129134
(JsonPath::Index(idx1), JsonPath::Index(idx2)) => idx1 == idx2,
135+
(JsonPath::Fn(fn1), JsonPath::Fn(fn2)) => fn2 == fn1,
130136
(_, _) => false
131137
}
132138
}

0 commit comments

Comments
 (0)