Skip to content

Commit 4645c33

Browse files
committed
feat: support operator: field-extractor
1 parent 77ad2db commit 4645c33

File tree

4 files changed

+83
-47
lines changed

4 files changed

+83
-47
lines changed

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,4 +127,6 @@ dmypy.json
127127

128128
# Pyre type checker
129129
.pyre/
130-
.vscode
130+
.vscode
131+
132+
tmp.py

README.md

Lines changed: 27 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55
- [Features](#features)
66
- [Examples](#examples)
77
- [Filter](#filter)
8-
- [Sort](#sort)
9-
- [Output mode: FIELD (In progress)](#output-mode-field-in-progress)
8+
- [Sorter](#sorter)
9+
- [Field-Extractor](#field-extractor)
1010

1111
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
1212

@@ -17,12 +17,12 @@ A more powerful JSONPath implementations in modern python.
1717
## Features
1818

1919
- [x] Light. (No need to install third-party dependencies.)
20-
- [x] Powerful filtering function, including multi-selection, inverse-selection filtering.
21-
- [x] Powerful sorting function, including sorting by multiple fields, ascending and descending order.
22-
- [x] Support output mode: VALUE
23-
- [ ] Support output mode: PATH
24-
- [ ] Support output mode: FIELD
25-
- [ ] Support parent operator
20+
- [x] Support basic semantics of JSONPath.
21+
- [x] Support output modes: VALUE, PATH.
22+
- [x] Support filter operator, including multi-selection, inverse-selection filtering.
23+
- [x] Support sorter operator, including sorting by multiple fields, ascending and descending order.
24+
- [ ] Support parent operator.
25+
- [ ] Support user-defined function.
2626

2727
## Examples
2828

@@ -122,33 +122,29 @@ data = {
122122
]
123123
```
124124

125-
### Field-Extractor (In progress)
125+
### Field-Extractor
126126

127127
```python
128-
>>> JSONPath("$.store.book[*][title,price]",result_type="FIELD").parse(data)
128+
>>> JSONPath("$.store.book[*](title,price)",result_type="FIELD").parse(data)
129129
```
130130

131131
```json
132-
{
133-
"store": {
134-
"book": [
135-
{
136-
"title": "Sayings of the Century",
137-
"price": 8.95
138-
},
139-
{
140-
"title": "Sword of Honour",
141-
"price": 12.99
142-
},
143-
{
144-
"title": "Moby Dick",
145-
"price": 8.99
146-
},
147-
{
148-
"title": "The Lord of the Rings",
149-
"price": 22.99
150-
}
151-
]
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
152148
}
153-
}
149+
]
154150
```

jsonpath/__init__.py

Lines changed: 22 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
11
"""
2-
Author : zhangxianbing1
2+
Author : zhangxianbing
33
Date : 2020-12-27 09:22:14
4-
LastEditors : zhangxianbing1
5-
LastEditTime : 2021-01-04 14:34:35
4+
LastEditors : zhangxianbing
5+
LastEditTime : 2021-01-04 15:25:08
66
Description : JSONPath
77
"""
88
__version__ = "0.0.3"
99
__author__ = "zhangxianbing"
1010

1111
import json
12+
import logging
1213
import os
1314
import re
14-
import logging
1515
from collections import defaultdict
1616
from typing import Union
1717

@@ -46,7 +46,6 @@ class ExprSyntaxError(Exception):
4646
class JSONPath:
4747
RESULT_TYPE = {
4848
"VALUE": "A list of specific values.",
49-
"FIELD": "A dict with specific fields.",
5049
"PATH": "All path of specific values.",
5150
}
5251

@@ -70,7 +69,7 @@ class JSONPath:
7069
steps: list
7170
lpath: int
7271
subx = defaultdict(list)
73-
result: Union[list, dict]
72+
result: list
7473
result_type: str
7574

7675
def __init__(self, expr: str):
@@ -82,16 +81,14 @@ def __init__(self, expr: str):
8281
def parse(self, obj, result_type="VALUE"):
8382
if not isinstance(obj, (list, dict)):
8483
raise TypeError("obj must be a list or a dict.")
84+
8585
if result_type not in JSONPath.RESULT_TYPE:
8686
raise ValueError(
8787
f"result_type must be one of {tuple(JSONPath.RESULT_TYPE.keys())}"
8888
)
8989
self.result_type = result_type
90-
if self.result_type == "FIELD":
91-
self.result = {}
92-
else:
93-
self.result = []
9490

91+
self.result = []
9592
self._trace(obj, 0, "$")
9693

9794
return self.result
@@ -188,8 +185,6 @@ def _trace(self, obj, i: int, path):
188185
self.result.append(obj)
189186
elif self.result_type == "PATH":
190187
self.result.append(path)
191-
elif self.result_type == "FIELD":
192-
pass
193188
LOG.debug(f"path: {path} | value: {obj}")
194189
return
195190

@@ -240,7 +235,7 @@ def _trace(self, obj, i: int, path):
240235
self._traverse(self._filter, obj, i + 1, path, step)
241236
return
242237

243-
# sort
238+
# sorter
244239
if step.startswith("/(") and step.endswith(")"):
245240
if isinstance(obj, list):
246241
obj = list(enumerate(obj))
@@ -253,12 +248,24 @@ def _trace(self, obj, i: int, path):
253248
for k, v in obj:
254249
self._trace(v, i + 1, f"{path}{JSONPath.SEP}{k}")
255250
else:
256-
raise ExprSyntaxError("sort operate must acting on list or dict")
251+
raise ExprSyntaxError("sorter must acting on list or dict")
257252
return
258253

254+
# field-extractor
255+
if step.startswith("(") and step.endswith(")"):
256+
if isinstance(obj, dict):
257+
obj_ = {}
258+
for k in step[1:-1].split(","):
259+
obj_[k] = obj.get(k)
260+
self._trace(obj_, i + 1, path)
261+
else:
262+
raise ExprSyntaxError("field-extractor must acting on list or dict")
263+
259264

260265
if __name__ == "__main__":
261266
with open("test/data/2.json", "rb") as f:
262267
d = json.load(f)
263-
D = JSONPath("$.book[/(price)].price").parse(d, "PATH")
268+
D = JSONPath("$.book[*].(title)").parse(d, "VALUE")
264269
print(D)
270+
for v in D:
271+
print(v)

test/conftest.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,37 @@
4848
["v0.0.1", "v1.0.0", "v1.0.2", "v1.0.3"],
4949
),
5050
TestCase("$.scores[/(score)].score", data, [60, 85, 90, 95, 100]),
51+
TestCase(
52+
"$.scores[/(score)].(score)",
53+
data,
54+
[
55+
{"score": 60},
56+
{"score": 85},
57+
{"score": 90},
58+
{"score": 95},
59+
{"score": 100},
60+
],
61+
),
62+
TestCase(
63+
"$.book[*].(title)",
64+
data,
65+
[
66+
{"title": "Sayings of the Century"},
67+
{"title": "Sword of Honour"},
68+
{"title": "Moby Dick"},
69+
{"title": "The Lord of the Rings"},
70+
],
71+
),
72+
TestCase(
73+
"$.book[/(category,price)].(title,price)",
74+
data,
75+
[
76+
{"title": "Moby Dick", "price": 8.99},
77+
{"title": "Sword of Honour", "price": 12.99},
78+
{"title": "The Lord of the Rings", "price": 22.99},
79+
{"title": "Sayings of the Century", "price": 8.95},
80+
],
81+
),
5182
]
5283
)
5384
def value_cases(request):

0 commit comments

Comments
 (0)