Skip to content

Commit 52b454f

Browse files
authored
Merge pull request #3 from zhangxianbing/dev
complete jsonpath syntax
2 parents 4d2467c + cca9583 commit 52b454f

File tree

2 files changed

+214
-120
lines changed

2 files changed

+214
-120
lines changed

README.md

Lines changed: 205 additions & 111 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
1-
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
2-
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
3-
41
- [jsonpath-python](#jsonpath-python)
52
- [Features](#features)
6-
- [Examples](#examples)
7-
- [Filter](#filter)
8-
- [Sorter](#sorter)
9-
- [Field-Extractor](#field-extractor)
10-
11-
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
3+
- [JSONPath Syntax](#jsonpath-syntax)
4+
- [Operators](#operators)
5+
- [Examples](#examples)
6+
- [Select Fields](#select-fields)
7+
- [Recursive Descent](#recursive-descent)
8+
- [Slice](#slice)
9+
- [Filter Expression](#filter-expression)
10+
- [Sorter Expression](#sorter-expression)
11+
- [Field-Extractor Expression](#field-extractor-expression)
12+
- [Appendix: Example JSON data:](#appendix-example-json-data)
13+
- [Todo List](#todo-list)
1214

1315
# jsonpath-python
1416

@@ -24,127 +26,219 @@ A more powerful JSONPath implementation in modern python.
2426
- [ ] Support parent operator.
2527
- [ ] Support user-defined function.
2628

27-
## Examples
29+
## JSONPath Syntax
30+
31+
The JSONPath syntax in this project borrows from [JSONPath - XPath for JSON](http://goessner.net/articles/JSONPath/) and is **modified** and **extended** on it.
32+
33+
### Operators
2834

29-
JSON format data:
35+
| Operator | Description |
36+
| ---------------- | ---------------------------------------------------------------------------- |
37+
| `$` | the root object/element |
38+
| `@` | the current object/element |
39+
| `.` or `[]` | child operator |
40+
| `..` | recursive descent |
41+
| `*` | wildcard |
42+
| `''` | (Experimental) wrap field with special character: dots(`.`) and space (` `). |
43+
| `start:end:step` | array slice operator (It's same as the slice in python) |
44+
| `?()` | applies a filter expression |
45+
| `/()` | applies a sorter expression |
46+
| `()` | applies a field-extractor expression |
47+
48+
### Examples
49+
50+
Before running the following example, please import this module and the example data:
3051

3152
```python
32-
data = {
33-
"store": {
34-
"book": [
35-
{
36-
"category": "reference",
37-
"author": "Nigel Rees",
38-
"title": "Sayings of the Century",
39-
"price": 8.95
40-
},
41-
{
42-
"category": "fiction",
43-
"author": "Evelyn Waugh",
44-
"title": "Sword of Honour",
45-
"price": 12.99
46-
},
47-
{
48-
"category": "fiction",
49-
"author": "Herman Melville",
50-
"title": "Moby Dick",
51-
"isbn": "0-553-21311-3",
52-
"price": 8.99
53-
},
54-
{
55-
"category": "fiction",
56-
"author": "J. R. R. Tolkien",
57-
"title": "The Lord of the Rings",
58-
"isbn": "0-395-19395-8",
59-
"price": 22.99
60-
}
61-
],
62-
"bicycle": {
63-
"color": "red",
64-
"price": 19.95
65-
}
66-
}
67-
}
53+
>>> from jsonpath import JSONPath
54+
55+
# For the data used in the following example, please refer to the Appendix part.
6856
```
6957

70-
### Filter
58+
#### Select Fields
59+
60+
Select a field:
7161

7262
```python
73-
>>> JSONPath('$.store.book[?(@.price>8.95 and @.price<=20 and @.title!="Sword of Honour")]').parse(data)
63+
>>> JSONPath("$.book").parse(data)
64+
[[{'category': 'reference', 'author': 'Nigel Rees', 'title': 'Sayings of the Century', 'price': 8.95, 'brand': {'version': 'v1.0.0'}}, {'category': 'fiction', 'author': 'Evelyn Waugh', 'title': 'Sword of Honour', 'price': 12.99, 'brand': {'version': 'v0.0.1'}}, {'category': 'fiction', 'author': 'Herman Melville', 'title': 'Moby Dick', 'isbn': '0-553-21311-3', 'price': 8.99, 'brand': {'version': 'v1.0.2'}}, {'category': 'fiction', 'author': 'J. R. R. Tolkien', 'title': 'The Lord of the Rings', 'isbn': '0-395-19395-8', 'price': 22.99, 'brand': {'version': 'v1.0.3'}}]]
65+
>>> JSONPath("$[book]").parse(data)
66+
[[{'category': 'reference', 'author': 'Nigel Rees', 'title': 'Sayings of the Century', 'price': 8.95, 'brand': {'version': 'v1.0.0'}}, {'category': 'fiction', 'author': 'Evelyn Waugh', 'title': 'Sword of Honour', 'price': 12.99, 'brand': {'version': 'v0.0.1'}}, {'category': 'fiction', 'author': 'Herman Melville', 'title': 'Moby Dick', 'isbn': '0-553-21311-3', 'price': 8.99, 'brand': {'version': 'v1.0.2'}}, {'category': 'fiction', 'author': 'J. R. R. Tolkien', 'title': 'The Lord of the Rings', 'isbn': '0-395-19395-8', 'price': 22.99, 'brand': {'version': 'v1.0.3'}}]]
7467
```
7568

76-
```json
77-
[
78-
{
79-
"category": "fiction",
80-
"author": "Herman Melville",
81-
"title": "Moby Dick",
82-
"isbn": "0-553-21311-3",
83-
"price": 8.99
84-
}
85-
]
69+
(**Experimental**) Select a field with special character: dots(`.`) and space (` `).
70+
71+
```python
72+
>>> JSONPath("$.'a.b c'").parse(data)
73+
['a.b c']
74+
>>> JSONPath("$['a.b c']").parse(data)
75+
['a.b c']
8676
```
8777

88-
### Sorter
78+
Select multiple fields:
8979

9080
```python
91-
>>> JSONPath("$.store.book[/(~category,price)]").parse(data)
81+
>>> JSONPath("$[bicycle,scores]").parse(data)
82+
[{'color': 'red', 'price': 19.95}, {'math': {'score': 100, 'avg': 60}, 'english': {'score': 95, 'avg': 80}, 'physic': {'score': 90, 'avg': 70}, 'chemistry': {'score': 85, 'avg': 80}, 'chinese': {'score': 60, 'avg': 75}}]
9283
```
9384

94-
```json
95-
[
96-
{
97-
"category": "reference",
98-
"author": "Nigel Rees",
99-
"title": "Sayings of the Century",
100-
"price": 8.95
101-
},
102-
{
103-
"category": "fiction",
104-
"author": "Herman Melville",
105-
"title": "Moby Dick",
106-
"isbn": "0-553-21311-3",
107-
"price": 8.99
108-
},
109-
{
110-
"category": "fiction",
111-
"author": "Evelyn Waugh",
112-
"title": "Sword of Honour",
113-
"price": 12.99
114-
},
115-
{
116-
"category": "fiction",
117-
"author": "J. R. R. Tolkien",
118-
"title": "The Lord of the Rings",
119-
"isbn": "0-395-19395-8",
120-
"price": 22.99
121-
}
122-
]
85+
Select all fields using wildcard `*`:
86+
87+
```python
88+
>>> JSONPath("$.*").parse(data)
89+
['a.b c', [{'category': 'reference', 'author': 'Nigel Rees', 'title': 'Sayings of the Century', 'price': 8.95, 'brand': {'version': 'v1.0.0'}}, {'category': 'fiction', 'author': 'Evelyn Waugh', 'title': 'Sword of Honour', 'price': 12.99, 'brand': {'version': 'v0.0.1'}}, {'category': 'fiction', 'author': 'Herman Melville', 'title': 'Moby Dick', 'isbn': '0-553-21311-3', 'price': 8.99, 'brand': {'version': 'v1.0.2'}}, {'category': 'fiction', 'author': 'J. R. R. Tolkien', 'title': 'The Lord of the Rings', 'isbn': '0-395-19395-8', 'price': 22.99, 'brand': {'version': 'v1.0.3'}}], {'color': 'red', 'price': 19.95}, {'math': {'score': 100, 'avg': 60}, 'english': {'score': 95, 'avg': 80}, 'physic': {'score': 90, 'avg': 70}, 'chemistry': {'score': 85, 'avg': 80}, 'chinese': {'score': 60, 'avg': 75}}]
12390
```
12491

125-
### Field-Extractor
92+
#### Recursive Descent
12693

12794
```python
128-
>>> JSONPath("$.store.book[*](title,price)",result_type="FIELD").parse(data)
95+
>>> JSONPath("$..price").parse(data)
96+
[8.95, 12.99, 8.99, 22.99, 19.95]
12997
```
13098

131-
```json
132-
[
133-
{
134-
"title": "Sayings of the Century",
135-
"price": 8.95
136-
},
137-
{
138-
"title": "Sword of Honour",
139-
"price": 12.99
140-
},
141-
{
142-
"title": "Moby Dick",
143-
"price": 8.99
144-
},
145-
{
146-
"title": "The Lord of the Rings",
147-
"price": 22.99
148-
}
149-
]
99+
#### Slice
100+
101+
Support python-like slice.
102+
103+
```python
104+
>>> JSONPath("$.book[1:3]").parse(data)
105+
[{'category': 'fiction', 'author': 'Evelyn Waugh', 'title': 'Sword of Honour', 'price': 12.99, 'brand': {'version': 'v0.0.1'}}, {'category': 'fiction', 'author': 'Herman Melville', 'title': 'Moby Dick', 'isbn': '0-553-21311-3', 'price': 8.99, 'brand': {'version': 'v1.0.2'}}]
106+
>>> JSONPath("$.book[1:-1]").parse(data)
107+
[{'category': 'fiction', 'author': 'Evelyn Waugh', 'title': 'Sword of Honour', 'price': 12.99, 'brand': {'version': 'v0.0.1'}}, {'category': 'fiction', 'author': 'Herman Melville', 'title': 'Moby Dick', 'isbn': '0-553-21311-3', 'price': 8.99, 'brand': {'version': 'v1.0.2'}}]
108+
>>> JSONPath("$.book[0:-1:2]").parse(data)
109+
[{'category': 'reference', 'author': 'Nigel Rees', 'title': 'Sayings of the Century', 'price': 8.95, 'brand': {'version': 'v1.0.0'}}, {'category': 'fiction', 'author': 'Herman Melville', 'title': 'Moby Dick', 'isbn': '0-553-21311-3', 'price': 8.99, 'brand': {'version': 'v1.0.2'}}]
110+
>>> JSONPath("$.book[-1:1]").parse(data)
111+
[]
112+
>>> JSONPath("$.book[-1:-11:3]").parse(data)
113+
[]
114+
>>> JSONPath("$.book[:]").parse(data)
115+
[{'category': 'reference', 'author': 'Nigel Rees', 'title': 'Sayings of the Century', 'price': 8.95, 'brand': {'version': 'v1.0.0'}}, {'category': 'fiction', 'author': 'Evelyn Waugh', 'title': 'Sword of Honour', 'price': 12.99, 'brand': {'version': 'v0.0.1'}}, {'category': 'fiction', 'author': 'Herman Melville', 'title': 'Moby Dick', 'isbn': '0-553-21311-3', 'price': 8.99, 'brand': {'version': 'v1.0.2'}}, {'category': 'fiction', 'author': 'J. R. R. Tolkien', 'title': 'The Lord of the Rings', 'isbn': '0-395-19395-8', 'price': 22.99, 'brand': {'version': 'v1.0.3'}}]
116+
>>> JSONPath("$.book[::-1]").parse(data)
117+
[{'category': 'fiction', 'author': 'J. R. R. Tolkien', 'title': 'The Lord of the Rings', 'isbn': '0-395-19395-8', 'price': 22.99, 'brand': {'version': 'v1.0.3'}}, {'category': 'fiction', 'author': 'Herman Melville', 'title': 'Moby Dick', 'isbn': '0-553-21311-3', 'price': 8.99, 'brand': {'version': 'v1.0.2'}}, {'category': 'fiction', 'author': 'Evelyn Waugh', 'title': 'Sword of Honour', 'price': 12.99, 'brand': {'version': 'v0.0.1'}}, {'category': 'reference', 'author': 'Nigel Rees', 'title': 'Sayings of the Century', 'price': 8.95, 'brand': {'version': 'v1.0.0'}}]
118+
150119
```
120+
121+
#### Filter Expression
122+
123+
Support all python comparison operators (`==`, `!=`, `<`, `>`, `>=`, `<=`), python membership operators (`in`, `not in`), python logical operators (`and`, `or`, `not`).
124+
125+
```python
126+
>>> JSONPath("$.book[?(@.price>8 and @.price<9)].price").parse(data)
127+
[8.95, 8.99]
128+
>>> JSONPath('$.book[?(@.category=="reference")].category').parse(data)
129+
['reference']
130+
>>> JSONPath('$.book[?(@.category!="reference" and @.price<9)].title').parse(data)
131+
['Moby Dick']
132+
>>> JSONPath('$.book[?(@.author=="Herman Melville" or @.author=="Evelyn Waugh")].author').parse(data)
133+
['Evelyn Waugh', 'Herman Melville']
134+
```
135+
136+
`Note`: You must use double quote(`""`) instead of single quote(`''`) to wrap the compared string, because single quote(`''`) has another usage in this JSONPath syntax .
137+
138+
#### Sorter Expression
139+
140+
Support sorting by multiple fields (using operator `,`) and reverse sort (using operator `~`).
141+
142+
```python
143+
>>> JSONPath("$.book[/(price)].price").parse(data)
144+
[8.95, 8.99, 12.99, 22.99]
145+
>>> JSONPath("$.book[/(~price)].price").parse(data)
146+
[22.99, 12.99, 8.99, 8.95]
147+
>>> JSONPath("$.book[/(category,price)].price").parse(data)
148+
[8.99, 12.99, 22.99, 8.95]
149+
>>> JSONPath("$.book[/(brand.version)].brand.version").parse(data)
150+
['v0.0.1', 'v1.0.0', 'v1.0.2', 'v1.0.3']
151+
>>> JSONPath("$.scores[/(score)].score").parse(data)
152+
[60, 85, 90, 95, 100]
153+
```
154+
155+
#### Field-Extractor Expression
156+
157+
Using `(field1,field2,…,filedn)` after a dict object to extract its fields.
158+
159+
```python
160+
>>> JSONPath("$.scores[/(score)].(score)").parse(data)
161+
[{'score': 60}, {'score': 85}, {'score': 90}, {'score': 95}, {'score': 100}]
162+
>>> JSONPath("$.book[/(category,price)].(title,price)").parse(data)
163+
[{'title': 'Moby Dick', 'price': 8.99}, {'title': 'Sword of Honour', 'price': 12.99}, {'title': 'The Lord of the Rings', 'price': 22.99}, {'title': 'Sayings of the Century', 'price': 8.95}]
164+
```
165+
166+
### Appendix: Example JSON data:
167+
168+
```python
169+
data = {
170+
"a.b c": "a.b c",
171+
"book": [
172+
{
173+
"category": "reference",
174+
"author": "Nigel Rees",
175+
"title": "Sayings of the Century",
176+
"price": 8.95,
177+
"brand": {
178+
"version": "v1.0.0"
179+
}
180+
},
181+
{
182+
"category": "fiction",
183+
"author": "Evelyn Waugh",
184+
"title": "Sword of Honour",
185+
"price": 12.99,
186+
"brand": {
187+
"version": "v0.0.1"
188+
}
189+
},
190+
{
191+
"category": "fiction",
192+
"author": "Herman Melville",
193+
"title": "Moby Dick",
194+
"isbn": "0-553-21311-3",
195+
"price": 8.99,
196+
"brand": {
197+
"version": "v1.0.2"
198+
}
199+
},
200+
{
201+
"category": "fiction",
202+
"author": "J. R. R. Tolkien",
203+
"title": "The Lord of the Rings",
204+
"isbn": "0-395-19395-8",
205+
"price": 22.99,
206+
"brand": {
207+
"version": "v1.0.3"
208+
}
209+
}
210+
],
211+
"bicycle": {
212+
"color": "red",
213+
"price": 19.95
214+
},
215+
"scores": {
216+
"math": {
217+
"score": 100,
218+
"avg": 60
219+
},
220+
"english": {
221+
"score": 95,
222+
"avg": 80
223+
},
224+
"physic": {
225+
"score": 90,
226+
"avg": 70
227+
},
228+
"chemistry": {
229+
"score": 85,
230+
"avg": 80
231+
},
232+
"chinese": {
233+
"score": 60,
234+
"avg": 75
235+
}
236+
}
237+
}
238+
```
239+
240+
## Todo List
241+
242+
- Syntax and character set (refer to k8s)
243+
244+
> The name segment is required and must be 63 characters or less, beginning and ending with an alphanumeric character (`[a-z0-9A-Z]`) with dashes (`-`), underscores (`_`), dots (`.`), and alphanumerics between.

0 commit comments

Comments
 (0)