simple_jsonpath 0.2.2__cp314-cp314-win32.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- simple_jsonpath/__init__.py +7 -0
- simple_jsonpath/_simple_jsonpath.cp314-win32.pyd +0 -0
- simple_jsonpath/_simple_jsonpath.pyi +16 -0
- simple_jsonpath/jsonpath.py +144 -0
- simple_jsonpath/py.typed +0 -0
- simple_jsonpath-0.2.2.dist-info/METADATA +173 -0
- simple_jsonpath-0.2.2.dist-info/RECORD +10 -0
- simple_jsonpath-0.2.2.dist-info/WHEEL +4 -0
- simple_jsonpath-0.2.2.dist-info/licenses/LICENSE +25 -0
- simple_jsonpath-0.2.2.dist-info/sboms/simple_jsonpath.cyclonedx.json +1280 -0
|
Binary file
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""A Python module for querying JSON data using JSONPath expressions."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class SimpleJsonPath:
|
|
5
|
+
"""A parser object that can be reused for multiple queries on the same JSON data."""
|
|
6
|
+
def __init__(self) -> None: ...
|
|
7
|
+
def set_data_from_json_str(self, input_data: str) -> None:
|
|
8
|
+
"""Set the JSON data for the parser from a JSON string."""
|
|
9
|
+
...
|
|
10
|
+
def find_from_set_data(self, path: str) -> str:
|
|
11
|
+
"""Find the value(s) in the JSON data that match the given JSONPath expression, using a cache for parsed paths."""
|
|
12
|
+
...
|
|
13
|
+
|
|
14
|
+
def find_location_from_set_data(self, path: str) -> str:
|
|
15
|
+
"""Find the value(s) in the JSON data that match the given JSONPath expression, along with their locations, using a cache for parsed paths."""
|
|
16
|
+
...
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
from ._simple_jsonpath import SimpleJsonPath as RustSimpleJsonPath
|
|
2
|
+
from typing import Any, Union
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
import builtins
|
|
5
|
+
import json
|
|
6
|
+
|
|
7
|
+
class PathComponentsIter:
|
|
8
|
+
def __init__(self, path: str, nodes: list[tuple[int, int]]):
|
|
9
|
+
self._current: int = 0
|
|
10
|
+
self._path: str = path
|
|
11
|
+
self._end: int = len(nodes)
|
|
12
|
+
self._items: list[tuple[int,int]] = nodes
|
|
13
|
+
|
|
14
|
+
def __next__(self) -> Union[str, int]:
|
|
15
|
+
|
|
16
|
+
if self._current >= self._end:
|
|
17
|
+
raise StopIteration
|
|
18
|
+
if self._current == 0:
|
|
19
|
+
self._current +=1
|
|
20
|
+
return "$"
|
|
21
|
+
else:
|
|
22
|
+
item = self._items[self._current]
|
|
23
|
+
if item[0] == 0:
|
|
24
|
+
self._current += 1
|
|
25
|
+
return item[1]
|
|
26
|
+
else:
|
|
27
|
+
self._current += 1
|
|
28
|
+
return self._path[item[0]:item[1]]
|
|
29
|
+
|
|
30
|
+
@dataclass(frozen=True)
|
|
31
|
+
class PathComponents:
|
|
32
|
+
_path: str
|
|
33
|
+
_items: list[tuple[int, int]]
|
|
34
|
+
|
|
35
|
+
def __len__(self) -> int:
|
|
36
|
+
return len(self._items)
|
|
37
|
+
|
|
38
|
+
def __iter__(self) -> PathComponentsIter:
|
|
39
|
+
return PathComponentsIter(self._path, self._items)
|
|
40
|
+
|
|
41
|
+
def __getitem__(self, index: int) -> Union[str, int]:
|
|
42
|
+
if index >= len(self._items):
|
|
43
|
+
raise IndexError("Index out of range")
|
|
44
|
+
elif index == 0:
|
|
45
|
+
return "$"
|
|
46
|
+
elif index < 0:
|
|
47
|
+
raise IndexError("Negative indexing is not supported")
|
|
48
|
+
else:
|
|
49
|
+
item = self._items[index]
|
|
50
|
+
if item[0] == 0:
|
|
51
|
+
return item[1]
|
|
52
|
+
else:
|
|
53
|
+
return self._path[item[0]:item[1]]
|
|
54
|
+
def __contains__(self, item: Union[int, str]) -> bool:
|
|
55
|
+
if isinstance(item, int):
|
|
56
|
+
for i in range(len(self._items)):
|
|
57
|
+
if i == 0:
|
|
58
|
+
continue
|
|
59
|
+
else:
|
|
60
|
+
if self._items[i][0] == 0 and self._items[i][1] == item:
|
|
61
|
+
return True
|
|
62
|
+
return False
|
|
63
|
+
else:
|
|
64
|
+
for i in range(len(self._items[1:])):
|
|
65
|
+
if i == 0:
|
|
66
|
+
if item == "$":
|
|
67
|
+
return True
|
|
68
|
+
else:
|
|
69
|
+
if self._path[self._items[i][0]:self._items[i][1]] == item:
|
|
70
|
+
return True
|
|
71
|
+
return False
|
|
72
|
+
|
|
73
|
+
class LocatedNode:
|
|
74
|
+
"""A struct to hold the located nodes found from a located JSONPath query."""
|
|
75
|
+
def __init__(self, full_path: str, path_components: list[tuple[int, int]], node: Union[str,int,float,bool,None,dict[str, Any], list[Any]]) -> None:
|
|
76
|
+
self._full_path: str = full_path
|
|
77
|
+
self._path_components: PathComponents = PathComponents(full_path, path_components)
|
|
78
|
+
self._node: Union[str,int,float,bool,None,dict[str, Any], list[Any]] = node
|
|
79
|
+
|
|
80
|
+
@builtins.property
|
|
81
|
+
def path_components(self) -> PathComponents:
|
|
82
|
+
"""An iterator that yields the path components of the last query result."""
|
|
83
|
+
return self._path_components
|
|
84
|
+
@builtins.property
|
|
85
|
+
def full_path(self) -> str:
|
|
86
|
+
"""The full path of the last query result."""
|
|
87
|
+
return self._full_path
|
|
88
|
+
@builtins.property
|
|
89
|
+
def node(self) -> Union[str,int,float,bool,None,dict[str, Any], list[Any]]:
|
|
90
|
+
"""The node value of the last query result."""
|
|
91
|
+
return self._node
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class JsonPath:
|
|
95
|
+
"""A simple JSONPath implementation for querying JSON data.
|
|
96
|
+
|
|
97
|
+
It uses a Rust backend for performance and supports caching of parsed
|
|
98
|
+
JSONPath expressions for repeated queries against the same JSON data.
|
|
99
|
+
"""
|
|
100
|
+
def __init__(self) -> None:
|
|
101
|
+
self._parser = RustSimpleJsonPath()
|
|
102
|
+
|
|
103
|
+
def set_data(self, input_data: Union[dict[str, Any], list[Any]]) -> None:
|
|
104
|
+
"""Set the JSON data for the parser from a Python dictionary or list.
|
|
105
|
+
|
|
106
|
+
Args:
|
|
107
|
+
input_data: The JSON data to set, as a Python dictionary or list.
|
|
108
|
+
|
|
109
|
+
Returns:
|
|
110
|
+
None
|
|
111
|
+
|
|
112
|
+
Raises:
|
|
113
|
+
ValueError: If the input data is not a valid JSON object or array.
|
|
114
|
+
"""
|
|
115
|
+
self._parser.set_data_from_json_str(json.dumps(input_data))
|
|
116
|
+
|
|
117
|
+
def find(self, path: str) -> list[Any]:
|
|
118
|
+
"""Find the value(s) in the JSON data that match the given JSONPath expression.
|
|
119
|
+
|
|
120
|
+
Args:
|
|
121
|
+
path: The JSONPath expression to evaluate.
|
|
122
|
+
|
|
123
|
+
Returns:
|
|
124
|
+
A list of values that match the JSONPath expression.
|
|
125
|
+
|
|
126
|
+
Raises:
|
|
127
|
+
ValueError: If the JSONPath expression is invalid.
|
|
128
|
+
"""
|
|
129
|
+
return json.loads(self._parser.find_from_set_data(path))
|
|
130
|
+
|
|
131
|
+
def find_located(self, path: str) -> list[LocatedNode]:
|
|
132
|
+
"""Find the value(s) in the JSON data that match the given JSONPath expression, along with their locations.
|
|
133
|
+
|
|
134
|
+
Args:
|
|
135
|
+
path: The JSONPath expression to evaluate.
|
|
136
|
+
|
|
137
|
+
Returns:
|
|
138
|
+
A list of LocatedNode objects that match the JSONPath expression.
|
|
139
|
+
|
|
140
|
+
Raises:
|
|
141
|
+
ValueError: If the JSONPath expression is invalid.
|
|
142
|
+
"""
|
|
143
|
+
result = json.loads(self._parser.find_location_from_set_data(path))
|
|
144
|
+
return [LocatedNode(item['full_path'], item['path_components'], item['node']) for item in result]
|
simple_jsonpath/py.typed
ADDED
|
File without changes
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: simple_jsonpath
|
|
3
|
+
Version: 0.2.2
|
|
4
|
+
Classifier: Intended Audience :: Developers
|
|
5
|
+
Classifier: Programming Language :: Rust
|
|
6
|
+
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
7
|
+
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
14
|
+
License-File: LICENSE
|
|
15
|
+
Summary: A simple - yet quick - JSONPath implementation for querying JSON data.
|
|
16
|
+
Keywords: jsonpath
|
|
17
|
+
Author-email: Jeremy Spencer <seojumper@gmail.com>
|
|
18
|
+
License-Expression: MIT
|
|
19
|
+
Requires-Python: >=3.10, <3.15
|
|
20
|
+
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
|
|
21
|
+
Project-URL: Homepage, https://github.com/seojumper/simple_jsonpath
|
|
22
|
+
|
|
23
|
+
# simple_jsonpath
|
|
24
|
+
|
|
25
|
+
## Installation
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
pip install simple_jsonpath
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## About
|
|
32
|
+
|
|
33
|
+
This module is a JSONPath [RFC9535 - JSONPath: Query Expressions for JSON](https://datatracker.ietf.org/doc/html/rfc9535) utility library.
|
|
34
|
+
|
|
35
|
+
## Use
|
|
36
|
+
|
|
37
|
+
This module exposes a single simple type - a ***JsonPath*** which has two methods after instantiation.
|
|
38
|
+
|
|
39
|
+
- ***set_data()***: sets the data that will be queried against. If multiple queries will be performed against a single piece of JSON data, this helps with the type conversion cost involved. This function can be called with providing new data whenever the inner data held is wished to be changed while retaining already complied paths (useful for querying multiple similarly structured documents).
|
|
40
|
+
- ***find()***: given a path that is wished to be found in the previously set data, this function will perform the query logic. Mulitple calls to **find()** will query against the previously 'set' data.
|
|
41
|
+
- ***find_located()***: given a path that is wished to be found in the previously set data, this function can return a list of ***LocatedNode*** objects. Each ***LocatedNode*** object will have attributes related to the path where the node was located as well as their corresponding data. This method is slower than ***find()***, so should ideally only be used when path information for the found nodes is needed.
|
|
42
|
+
|
|
43
|
+
## Examples
|
|
44
|
+
|
|
45
|
+
### 'Find' Example
|
|
46
|
+
|
|
47
|
+
```python
|
|
48
|
+
from simple_jsonpath import JsonPath
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
json_data = {
|
|
52
|
+
"address": {
|
|
53
|
+
"prefix-list": [
|
|
54
|
+
{
|
|
55
|
+
"prefix": "2001:db8::1/64",
|
|
56
|
+
"eui-64": [
|
|
57
|
+
None
|
|
58
|
+
]
|
|
59
|
+
}
|
|
60
|
+
],
|
|
61
|
+
"link-local-address": [
|
|
62
|
+
{
|
|
63
|
+
"address": "fe80::1",
|
|
64
|
+
"link-local": [
|
|
65
|
+
None
|
|
66
|
+
]
|
|
67
|
+
}
|
|
68
|
+
]
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
# Instantiates the primary class
|
|
73
|
+
finder = JsonPath()
|
|
74
|
+
|
|
75
|
+
# Sets the data that is desired to be queried against
|
|
76
|
+
finder.set_data(json_data)
|
|
77
|
+
|
|
78
|
+
# A path is provided to query against the 'set' data. The path is internally parsed > used to qeury against the 'set' dataset.
|
|
79
|
+
# Notice that this implementaion allows for escaping of specials characters shorthand path syntax with single or double quotes
|
|
80
|
+
results = finder.find("$.address.'prefix-list'[*].prefix")
|
|
81
|
+
|
|
82
|
+
for data in results:
|
|
83
|
+
# Access the found node.
|
|
84
|
+
print(f"{data}")
|
|
85
|
+
# 2001:db8::1/64
|
|
86
|
+
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
The inner implementation stores previously parsed 'paths'. This allows repeatedly used paths to bypass the parsing step invovled.
|
|
90
|
+
|
|
91
|
+
This is ideal for situations where multiple similar JSON documents will be searched in succession.
|
|
92
|
+
|
|
93
|
+
The same ***JsonPath*** object can then be reused with new data sets by calling ***set_data()*** on it again, and any previously parsed paths by the object will be retained.
|
|
94
|
+
|
|
95
|
+
Only when moving onto data of differing structure would it be potentially advisable to instantiate a new ***JsonPath*** object.
|
|
96
|
+
|
|
97
|
+
### 'Find Located' Example
|
|
98
|
+
|
|
99
|
+
```python
|
|
100
|
+
from simple_jsonpath import JsonPath, LocatedNode
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
json_data = {
|
|
104
|
+
"items": [
|
|
105
|
+
{
|
|
106
|
+
"address": {
|
|
107
|
+
"prefix-list": [
|
|
108
|
+
{
|
|
109
|
+
"prefix": "2001:db8::1/64",
|
|
110
|
+
"eui-64": [
|
|
111
|
+
None
|
|
112
|
+
]
|
|
113
|
+
}
|
|
114
|
+
],
|
|
115
|
+
"link-local-address": [
|
|
116
|
+
{
|
|
117
|
+
"address": "fe80::1",
|
|
118
|
+
"link-local": [
|
|
119
|
+
None
|
|
120
|
+
]
|
|
121
|
+
}
|
|
122
|
+
]
|
|
123
|
+
}
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
"address": {
|
|
127
|
+
"prefix-list": [
|
|
128
|
+
{
|
|
129
|
+
"prefix": "2001:db8::1/64",
|
|
130
|
+
"eui-64": [
|
|
131
|
+
None
|
|
132
|
+
]
|
|
133
|
+
}
|
|
134
|
+
],
|
|
135
|
+
"link-local-address": [
|
|
136
|
+
{
|
|
137
|
+
"address": "fe80::1",
|
|
138
|
+
"link-local": [
|
|
139
|
+
None
|
|
140
|
+
]
|
|
141
|
+
}
|
|
142
|
+
]
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
]
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
# Instantiates the primary class
|
|
149
|
+
finder = JsonPath()
|
|
150
|
+
|
|
151
|
+
# Sets the data that is desired to be queried against
|
|
152
|
+
finder.set_data(json_data)
|
|
153
|
+
|
|
154
|
+
# Now we are interested in the path information where matches were found as well as the data
|
|
155
|
+
results: list[LocatedNode] = finder.find_located("$.items[*].address.'prefix-list'[*].prefix")
|
|
156
|
+
|
|
157
|
+
# Iterate through each found LocatedNode object
|
|
158
|
+
for data in results:
|
|
159
|
+
|
|
160
|
+
# Print the normalized full path where the node was found
|
|
161
|
+
print(f"{data.full_path}")
|
|
162
|
+
# $['items'][0]['address']['prefix-list'][0]['prefix']
|
|
163
|
+
|
|
164
|
+
# Iterate over the components of the found path
|
|
165
|
+
# Returned elements will either be a 'str' for keys or 'int' for index values
|
|
166
|
+
print(f"{', '.join([str(component) for component in data.path_components])}")
|
|
167
|
+
# $, items, 0, adddress, prefix-list, 0, prefix
|
|
168
|
+
|
|
169
|
+
# Access the found node.
|
|
170
|
+
print(f"{data.node}")
|
|
171
|
+
# 2001:db8::1/64
|
|
172
|
+
```
|
|
173
|
+
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
simple_jsonpath/__init__.py,sha256=OofAgjl5Qzj6FaVVziW1JD8uhomAY6IhigeldJGbCrY,169
|
|
2
|
+
simple_jsonpath/_simple_jsonpath.cp314-win32.pyd,sha256=Ln0d1d56hm5OfQVTF3cJap2awgEc_l5My44pPB_JJmo,1455616
|
|
3
|
+
simple_jsonpath/_simple_jsonpath.pyi,sha256=xacGn97K908o7E1oYr4ZIdRgQuKr3W25rQCXPHB7R3M,791
|
|
4
|
+
simple_jsonpath/jsonpath.py,sha256=mfUInqb8qJ_wUbu95_MvM75zOlrRYnSOHjOOTRyBhnM,5294
|
|
5
|
+
simple_jsonpath/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
|
+
simple_jsonpath-0.2.2.dist-info/METADATA,sha256=jmMmt3Vr_rGG7V_utkoA39HtA36KoT2dwpRw-z9vVq8,6152
|
|
7
|
+
simple_jsonpath-0.2.2.dist-info/WHEEL,sha256=0RZTOoYFKCMMSuuhCfCow7TTDL6bY1Mq-hzasewFGUs,93
|
|
8
|
+
simple_jsonpath-0.2.2.dist-info/licenses/LICENSE,sha256=wFYBgkhw1xlkSCQiuXbjktNcYtLoxAOAnSVyKJRryOw,1081
|
|
9
|
+
simple_jsonpath-0.2.2.dist-info/sboms/simple_jsonpath.cyclonedx.json,sha256=UYAQhCBCpJo9CJO_xS3dJP-rSBIzN5qs65ULnoeD-3g,40961
|
|
10
|
+
simple_jsonpath-0.2.2.dist-info/RECORD,,
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
Copyright (c) 2026 Jeremy Spencer
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any
|
|
4
|
+
person obtaining a copy of this software and associated
|
|
5
|
+
documentation files (the "Software"), to deal in the
|
|
6
|
+
Software without restriction, including without
|
|
7
|
+
limitation the rights to use, copy, modify, merge,
|
|
8
|
+
publish, distribute, sublicense, and/or sell copies of
|
|
9
|
+
the Software, and to permit persons to whom the Software
|
|
10
|
+
is furnished to do so, subject to the following
|
|
11
|
+
conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice
|
|
14
|
+
shall be included in all copies or substantial portions
|
|
15
|
+
of the Software.
|
|
16
|
+
|
|
17
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
|
|
18
|
+
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
|
19
|
+
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
|
20
|
+
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
|
21
|
+
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
|
22
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
23
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
|
|
24
|
+
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
25
|
+
DEALINGS IN THE SOFTWARE.
|