simple_jsonpath 0.3.3__tar.gz → 0.4.3__tar.gz
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-0.3.3 → simple_jsonpath-0.4.3}/.gitignore +2 -1
- {simple_jsonpath-0.3.3 → simple_jsonpath-0.4.3}/Cargo.lock +129 -1
- {simple_jsonpath-0.3.3 → simple_jsonpath-0.4.3}/Cargo.toml +2 -2
- {simple_jsonpath-0.3.3 → simple_jsonpath-0.4.3}/PKG-INFO +9 -4
- {simple_jsonpath-0.3.3 → simple_jsonpath-0.4.3}/README.md +8 -3
- simple_jsonpath-0.4.3/python/simple_jsonpath/__init__.py +5 -0
- {simple_jsonpath-0.3.3 → simple_jsonpath-0.4.3}/python/simple_jsonpath/_simple_jsonpath.pyi +17 -5
- {simple_jsonpath-0.3.3 → simple_jsonpath-0.4.3}/python/simple_jsonpath/jsonpath.py +50 -103
- {simple_jsonpath-0.3.3 → simple_jsonpath-0.4.3}/src/lib.rs +160 -135
- simple_jsonpath-0.3.3/python/simple_jsonpath/__init__.py +0 -7
- {simple_jsonpath-0.3.3 → simple_jsonpath-0.4.3}/.github/workflows/CI.yml +0 -0
- {simple_jsonpath-0.3.3 → simple_jsonpath-0.4.3}/LICENSE +0 -0
- {simple_jsonpath-0.3.3 → simple_jsonpath-0.4.3}/pyproject.toml +0 -0
- {simple_jsonpath-0.3.3 → simple_jsonpath-0.4.3}/python/simple_jsonpath/py.typed +0 -0
|
@@ -2,6 +2,18 @@
|
|
|
2
2
|
# It is not intended for manual editing.
|
|
3
3
|
version = 4
|
|
4
4
|
|
|
5
|
+
[[package]]
|
|
6
|
+
name = "ahash"
|
|
7
|
+
version = "0.8.12"
|
|
8
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
9
|
+
checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75"
|
|
10
|
+
dependencies = [
|
|
11
|
+
"cfg-if",
|
|
12
|
+
"once_cell",
|
|
13
|
+
"version_check",
|
|
14
|
+
"zerocopy",
|
|
15
|
+
]
|
|
16
|
+
|
|
5
17
|
[[package]]
|
|
6
18
|
name = "aho-corasick"
|
|
7
19
|
version = "1.1.4"
|
|
@@ -11,6 +23,18 @@ dependencies = [
|
|
|
11
23
|
"memchr",
|
|
12
24
|
]
|
|
13
25
|
|
|
26
|
+
[[package]]
|
|
27
|
+
name = "bitflags"
|
|
28
|
+
version = "2.11.0"
|
|
29
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
30
|
+
checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af"
|
|
31
|
+
|
|
32
|
+
[[package]]
|
|
33
|
+
name = "byteorder"
|
|
34
|
+
version = "1.5.0"
|
|
35
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
36
|
+
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
|
37
|
+
|
|
14
38
|
[[package]]
|
|
15
39
|
name = "cfg-if"
|
|
16
40
|
version = "1.0.4"
|
|
@@ -108,12 +132,27 @@ version = "1.0.17"
|
|
|
108
132
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
109
133
|
checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2"
|
|
110
134
|
|
|
135
|
+
[[package]]
|
|
136
|
+
name = "lazy_static"
|
|
137
|
+
version = "1.5.0"
|
|
138
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
139
|
+
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
|
140
|
+
|
|
111
141
|
[[package]]
|
|
112
142
|
name = "libc"
|
|
113
143
|
version = "0.2.183"
|
|
114
144
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
115
145
|
checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d"
|
|
116
146
|
|
|
147
|
+
[[package]]
|
|
148
|
+
name = "lock_api"
|
|
149
|
+
version = "0.4.14"
|
|
150
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
151
|
+
checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965"
|
|
152
|
+
dependencies = [
|
|
153
|
+
"scopeguard",
|
|
154
|
+
]
|
|
155
|
+
|
|
117
156
|
[[package]]
|
|
118
157
|
name = "memchr"
|
|
119
158
|
version = "2.8.0"
|
|
@@ -142,6 +181,29 @@ version = "1.21.4"
|
|
|
142
181
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
143
182
|
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
|
|
144
183
|
|
|
184
|
+
[[package]]
|
|
185
|
+
name = "parking_lot"
|
|
186
|
+
version = "0.12.5"
|
|
187
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
188
|
+
checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a"
|
|
189
|
+
dependencies = [
|
|
190
|
+
"lock_api",
|
|
191
|
+
"parking_lot_core",
|
|
192
|
+
]
|
|
193
|
+
|
|
194
|
+
[[package]]
|
|
195
|
+
name = "parking_lot_core"
|
|
196
|
+
version = "0.9.12"
|
|
197
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
198
|
+
checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1"
|
|
199
|
+
dependencies = [
|
|
200
|
+
"cfg-if",
|
|
201
|
+
"libc",
|
|
202
|
+
"redox_syscall",
|
|
203
|
+
"smallvec",
|
|
204
|
+
"windows-link",
|
|
205
|
+
]
|
|
206
|
+
|
|
145
207
|
[[package]]
|
|
146
208
|
name = "pin-project-lite"
|
|
147
209
|
version = "0.2.17"
|
|
@@ -240,6 +302,15 @@ dependencies = [
|
|
|
240
302
|
"proc-macro2",
|
|
241
303
|
]
|
|
242
304
|
|
|
305
|
+
[[package]]
|
|
306
|
+
name = "redox_syscall"
|
|
307
|
+
version = "0.5.18"
|
|
308
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
309
|
+
checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
|
|
310
|
+
dependencies = [
|
|
311
|
+
"bitflags",
|
|
312
|
+
]
|
|
313
|
+
|
|
243
314
|
[[package]]
|
|
244
315
|
name = "regex"
|
|
245
316
|
version = "1.12.3"
|
|
@@ -319,6 +390,12 @@ version = "1.0.22"
|
|
|
319
390
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
320
391
|
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
|
|
321
392
|
|
|
393
|
+
[[package]]
|
|
394
|
+
name = "scopeguard"
|
|
395
|
+
version = "1.2.0"
|
|
396
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
397
|
+
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
|
398
|
+
|
|
322
399
|
[[package]]
|
|
323
400
|
name = "semver"
|
|
324
401
|
version = "1.0.27"
|
|
@@ -416,7 +493,7 @@ dependencies = [
|
|
|
416
493
|
|
|
417
494
|
[[package]]
|
|
418
495
|
name = "simple_jsonpath"
|
|
419
|
-
version = "0.
|
|
496
|
+
version = "0.4.3"
|
|
420
497
|
dependencies = [
|
|
421
498
|
"pyo3",
|
|
422
499
|
"rstest",
|
|
@@ -424,6 +501,7 @@ dependencies = [
|
|
|
424
501
|
"serde_json",
|
|
425
502
|
"serde_json_path",
|
|
426
503
|
"serde_json_path_core",
|
|
504
|
+
"ustr",
|
|
427
505
|
]
|
|
428
506
|
|
|
429
507
|
[[package]]
|
|
@@ -432,6 +510,12 @@ version = "0.4.12"
|
|
|
432
510
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
433
511
|
checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
|
|
434
512
|
|
|
513
|
+
[[package]]
|
|
514
|
+
name = "smallvec"
|
|
515
|
+
version = "1.15.1"
|
|
516
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
517
|
+
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
|
|
518
|
+
|
|
435
519
|
[[package]]
|
|
436
520
|
name = "syn"
|
|
437
521
|
version = "2.0.117"
|
|
@@ -505,6 +589,30 @@ version = "1.0.24"
|
|
|
505
589
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
506
590
|
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
|
|
507
591
|
|
|
592
|
+
[[package]]
|
|
593
|
+
name = "ustr"
|
|
594
|
+
version = "1.1.0"
|
|
595
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
596
|
+
checksum = "18b19e258aa08450f93369cf56dd78063586adf19e92a75b338a800f799a0208"
|
|
597
|
+
dependencies = [
|
|
598
|
+
"ahash",
|
|
599
|
+
"byteorder",
|
|
600
|
+
"lazy_static",
|
|
601
|
+
"parking_lot",
|
|
602
|
+
]
|
|
603
|
+
|
|
604
|
+
[[package]]
|
|
605
|
+
name = "version_check"
|
|
606
|
+
version = "0.9.5"
|
|
607
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
608
|
+
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
|
609
|
+
|
|
610
|
+
[[package]]
|
|
611
|
+
name = "windows-link"
|
|
612
|
+
version = "0.2.1"
|
|
613
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
614
|
+
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
|
615
|
+
|
|
508
616
|
[[package]]
|
|
509
617
|
name = "winnow"
|
|
510
618
|
version = "0.7.15"
|
|
@@ -514,6 +622,26 @@ dependencies = [
|
|
|
514
622
|
"memchr",
|
|
515
623
|
]
|
|
516
624
|
|
|
625
|
+
[[package]]
|
|
626
|
+
name = "zerocopy"
|
|
627
|
+
version = "0.8.47"
|
|
628
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
629
|
+
checksum = "efbb2a062be311f2ba113ce66f697a4dc589f85e78a4aea276200804cea0ed87"
|
|
630
|
+
dependencies = [
|
|
631
|
+
"zerocopy-derive",
|
|
632
|
+
]
|
|
633
|
+
|
|
634
|
+
[[package]]
|
|
635
|
+
name = "zerocopy-derive"
|
|
636
|
+
version = "0.8.47"
|
|
637
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
638
|
+
checksum = "0e8bc7269b54418e7aeeef514aa68f8690b8c0489a06b0136e5f57c4c5ccab89"
|
|
639
|
+
dependencies = [
|
|
640
|
+
"proc-macro2",
|
|
641
|
+
"quote",
|
|
642
|
+
"syn",
|
|
643
|
+
]
|
|
644
|
+
|
|
517
645
|
[[package]]
|
|
518
646
|
name = "zmij"
|
|
519
647
|
version = "1.0.21"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[package]
|
|
2
2
|
name = "simple_jsonpath"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.4.3"
|
|
4
4
|
edition = "2024"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
|
|
@@ -15,7 +15,7 @@ serde_json_path = { git = "https://github.com/seojumper/serde_json_path.git", br
|
|
|
15
15
|
serde_json_path_core = { git = "https://github.com/seojumper/serde_json_path.git", branch = "quotes" }
|
|
16
16
|
serde_json = "1.0.149"
|
|
17
17
|
serde = { version = "1.0.228", features = ["derive"] }
|
|
18
|
-
|
|
18
|
+
ustr = "1.1.0"
|
|
19
19
|
|
|
20
20
|
[dev-dependencies]
|
|
21
21
|
rstest = "0.26.1"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: simple_jsonpath
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.3
|
|
4
4
|
Classifier: Intended Audience :: Developers
|
|
5
5
|
Classifier: Programming Language :: Rust
|
|
6
6
|
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
@@ -31,7 +31,8 @@ pip install simple_jsonpath
|
|
|
31
31
|
|
|
32
32
|
## About
|
|
33
33
|
|
|
34
|
-
This module is a JSONPath [RFC9535 - JSONPath: Query Expressions for JSON](https://datatracker.ietf.org/doc/html/rfc9535) utility library.
|
|
34
|
+
This module is a JSONPath [RFC9535 - JSONPath: Query Expressions for JSON](https://datatracker.ietf.org/doc/html/rfc9535) utility library that supports performing querying for data in a JSON document. It does **NOT** supporting modifying data
|
|
35
|
+
in place.
|
|
35
36
|
|
|
36
37
|
## Use
|
|
37
38
|
|
|
@@ -182,12 +183,16 @@ results: list[LocatedNode] = finder.find_located("$.items[*].address.'prefix-lis
|
|
|
182
183
|
for data in results:
|
|
183
184
|
|
|
184
185
|
# Print the normalized full path where the node was found
|
|
185
|
-
print(f"{data.
|
|
186
|
+
print(f"{data.path}")
|
|
186
187
|
# $['items'][0]['address']['prefix-list'][0]['prefix']
|
|
187
188
|
|
|
189
|
+
# Print the normalized full path of the parent where the node was found
|
|
190
|
+
print(f"{data.parent_path}")
|
|
191
|
+
# $['items'][0]['address']['prefix-list'][0]
|
|
192
|
+
|
|
188
193
|
# Iterate over the components of the found path
|
|
189
194
|
# Returned elements will either be a 'str' for keys or 'int' for index values
|
|
190
|
-
print(f"{', '.join([str(component) for component in data.
|
|
195
|
+
print(f"{', '.join([str(component) for component in data.path])}")
|
|
191
196
|
# $, items, 0, adddress, prefix-list, 0, prefix
|
|
192
197
|
|
|
193
198
|
# Access the found node.
|
|
@@ -8,7 +8,8 @@ pip install simple_jsonpath
|
|
|
8
8
|
|
|
9
9
|
## About
|
|
10
10
|
|
|
11
|
-
This module is a JSONPath [RFC9535 - JSONPath: Query Expressions for JSON](https://datatracker.ietf.org/doc/html/rfc9535) utility library.
|
|
11
|
+
This module is a JSONPath [RFC9535 - JSONPath: Query Expressions for JSON](https://datatracker.ietf.org/doc/html/rfc9535) utility library that supports performing querying for data in a JSON document. It does **NOT** supporting modifying data
|
|
12
|
+
in place.
|
|
12
13
|
|
|
13
14
|
## Use
|
|
14
15
|
|
|
@@ -159,12 +160,16 @@ results: list[LocatedNode] = finder.find_located("$.items[*].address.'prefix-lis
|
|
|
159
160
|
for data in results:
|
|
160
161
|
|
|
161
162
|
# Print the normalized full path where the node was found
|
|
162
|
-
print(f"{data.
|
|
163
|
+
print(f"{data.path}")
|
|
163
164
|
# $['items'][0]['address']['prefix-list'][0]['prefix']
|
|
164
165
|
|
|
166
|
+
# Print the normalized full path of the parent where the node was found
|
|
167
|
+
print(f"{data.parent_path}")
|
|
168
|
+
# $['items'][0]['address']['prefix-list'][0]
|
|
169
|
+
|
|
165
170
|
# Iterate over the components of the found path
|
|
166
171
|
# Returned elements will either be a 'str' for keys or 'int' for index values
|
|
167
|
-
print(f"{', '.join([str(component) for component in data.
|
|
172
|
+
print(f"{', '.join([str(component) for component in data.path])}")
|
|
168
173
|
# $, items, 0, adddress, prefix-list, 0, prefix
|
|
169
174
|
|
|
170
175
|
# Access the found node.
|
|
@@ -1,11 +1,24 @@
|
|
|
1
1
|
"""A Python module for querying JSON data using JSONPath expressions."""
|
|
2
2
|
|
|
3
|
-
from typing import Any
|
|
3
|
+
from typing import Any, Union, Optional
|
|
4
|
+
|
|
5
|
+
class Path:
|
|
6
|
+
"""Object that represents the JSONPath where a node was found."""
|
|
7
|
+
@classmethod
|
|
8
|
+
def parent_path(cls) -> Optional[Path]:
|
|
9
|
+
"""Returns the parent path to this path item.
|
|
10
|
+
|
|
11
|
+
Returns:
|
|
12
|
+
Optional[Path]
|
|
13
|
+
"""
|
|
14
|
+
...
|
|
15
|
+
def __getitem__(self, index: int) -> Union[int, str]: ...
|
|
16
|
+
def __str__(self) -> str: ...
|
|
17
|
+
def __repr__(self) -> str: ...
|
|
4
18
|
|
|
5
19
|
class SimpleJsonPath:
|
|
6
20
|
"""A parser object that can be reused for multiple queries on the same JSON data."""
|
|
7
21
|
def __init__(self) -> None: ...
|
|
8
|
-
|
|
9
22
|
def child(self, value: str) -> SimpleJsonPath:
|
|
10
23
|
"""Spawn a child instance fo the Parser"""
|
|
11
24
|
...
|
|
@@ -15,10 +28,9 @@ class SimpleJsonPath:
|
|
|
15
28
|
def set_data(self, input_data: bytes) -> None:
|
|
16
29
|
"""Set the JSON data for the parser from a JSON string."""
|
|
17
30
|
...
|
|
18
|
-
def find(self, path: str) -> list[Any]:
|
|
31
|
+
def find(self, path: str) -> list[Any]:
|
|
19
32
|
"""Find the value(s) in the JSON data that match the given JSONPath expression, using a cache for parsed paths."""
|
|
20
33
|
...
|
|
21
|
-
|
|
22
|
-
def find_located(self, path: str) -> list[Any]:
|
|
34
|
+
def find_located(self, path: str) -> list[tuple[Path, Any]]:
|
|
23
35
|
"""Find the value(s) in the JSON data that match the given JSONPath expression, along with their locations, using a cache for parsed paths."""
|
|
24
36
|
...
|
|
@@ -1,110 +1,54 @@
|
|
|
1
|
-
from ._simple_jsonpath import SimpleJsonPath as RustSimpleJsonPath
|
|
1
|
+
from ._simple_jsonpath import SimpleJsonPath as RustSimpleJsonPath, Path
|
|
2
2
|
import sys
|
|
3
|
+
|
|
3
4
|
if sys.version_info >= (3, 11):
|
|
4
|
-
from typing import Self, Union, Any
|
|
5
|
+
from typing import Self, Union, Any, Optional
|
|
5
6
|
else:
|
|
6
|
-
from typing_extensions import Self, Union, Any
|
|
7
|
+
from typing_extensions import Self, Union, Any, Optional
|
|
7
8
|
import orjson
|
|
8
|
-
from dataclasses import dataclass
|
|
9
9
|
import builtins
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
class PathComponentsIter:
|
|
13
|
-
def __init__(self, path: str, nodes: list[tuple[int, int]]):
|
|
14
|
-
self._current: int = 0
|
|
15
|
-
self._path: str = path
|
|
16
|
-
self._end: int = len(nodes)
|
|
17
|
-
self._items: list[tuple[int,int]] = nodes
|
|
18
|
-
|
|
19
|
-
def __next__(self) -> Union[str, int]:
|
|
20
|
-
if self._current >= self._end:
|
|
21
|
-
raise StopIteration
|
|
22
|
-
if self._current == 0:
|
|
23
|
-
self._current +=1
|
|
24
|
-
return "$"
|
|
25
|
-
else:
|
|
26
|
-
item = self._items[self._current]
|
|
27
|
-
if item[0] == 0:
|
|
28
|
-
self._current += 1
|
|
29
|
-
return item[1]
|
|
30
|
-
else:
|
|
31
|
-
self._current += 1
|
|
32
|
-
return self._path[item[0]:item[1]]
|
|
33
|
-
|
|
34
|
-
@dataclass(frozen=True)
|
|
35
|
-
class PathComponents:
|
|
36
|
-
_path: str
|
|
37
|
-
_items: list[tuple[int, int]]
|
|
38
|
-
|
|
39
|
-
def __len__(self) -> int:
|
|
40
|
-
return len(self._items)
|
|
41
|
-
|
|
42
|
-
def __iter__(self) -> PathComponentsIter:
|
|
43
|
-
return PathComponentsIter(self._path, self._items)
|
|
44
|
-
|
|
45
|
-
def __getitem__(self, index: int) -> Union[str, int]:
|
|
46
|
-
if index >= len(self._items):
|
|
47
|
-
raise IndexError("Index out of range")
|
|
48
|
-
elif index == 0:
|
|
49
|
-
return "$"
|
|
50
|
-
elif index < 0:
|
|
51
|
-
raise IndexError("Negative indexing is not supported")
|
|
52
|
-
else:
|
|
53
|
-
item = self._items[index]
|
|
54
|
-
if item[0] == 0:
|
|
55
|
-
return item[1]
|
|
56
|
-
else:
|
|
57
|
-
return self._path[item[0]:item[1]]
|
|
58
|
-
def __contains__(self, item: Union[int, str]) -> bool:
|
|
59
|
-
if isinstance(item, int):
|
|
60
|
-
for i in range(len(self._items)):
|
|
61
|
-
if i == 0:
|
|
62
|
-
continue
|
|
63
|
-
else:
|
|
64
|
-
if self._items[i][0] == 0 and self._items[i][1] == item:
|
|
65
|
-
return True
|
|
66
|
-
return False
|
|
67
|
-
else:
|
|
68
|
-
for i in range(len(self._items[1:])):
|
|
69
|
-
if i == 0:
|
|
70
|
-
if item == "$":
|
|
71
|
-
return True
|
|
72
|
-
else:
|
|
73
|
-
if self._path[self._items[i][0]:self._items[i][1]] == item:
|
|
74
|
-
return True
|
|
75
|
-
return False
|
|
76
|
-
|
|
10
|
+
|
|
11
|
+
|
|
77
12
|
class LocatedNode:
|
|
78
|
-
"""A
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
self
|
|
82
|
-
|
|
13
|
+
"""A class that represents a node found in the JSON data along with its location path."""
|
|
14
|
+
|
|
15
|
+
def __init__(
|
|
16
|
+
self,
|
|
17
|
+
path: Path,
|
|
18
|
+
node: Union[str, int, float, bool, None, dict[str, Any], list[Any]],
|
|
19
|
+
) -> None:
|
|
20
|
+
self._path: Path = path
|
|
21
|
+
self._node: Union[str, int, float, bool, None, dict[str, Any], list[Any]] = node
|
|
83
22
|
|
|
84
23
|
@builtins.property
|
|
85
|
-
def
|
|
86
|
-
"""An iterator that yields the path components of the last query result.
|
|
87
|
-
|
|
24
|
+
def path(self) -> Path:
|
|
25
|
+
"""An iterator that yields the path components of the last query result.
|
|
26
|
+
|
|
27
|
+
The full path can be converted to a str through the str() method against this object.
|
|
28
|
+
"""
|
|
29
|
+
return self._path
|
|
30
|
+
|
|
88
31
|
@builtins.property
|
|
89
|
-
def
|
|
90
|
-
"""The full path of the last query result."""
|
|
91
|
-
return self._full_path
|
|
92
|
-
@builtins.property
|
|
93
|
-
def node(self) -> Union[str,int,float,bool,None,dict[str, Any], list[Any]]:
|
|
32
|
+
def node(self) -> Union[str, int, float, bool, None, dict[str, Any], list[Any]]:
|
|
94
33
|
"""The node value of the last query result."""
|
|
95
34
|
return self._node
|
|
35
|
+
|
|
36
|
+
@builtins.property
|
|
37
|
+
def parent_path(self) -> Optional[Path]:
|
|
38
|
+
"""The parent path of the last query result."""
|
|
39
|
+
return self._path.parent_path()
|
|
96
40
|
|
|
97
41
|
|
|
98
42
|
class JsonPath:
|
|
99
43
|
"""A simple JSONPath implementation for querying JSON data.
|
|
100
|
-
|
|
44
|
+
|
|
101
45
|
It uses a Rust backend for performance and supports caching of parsed
|
|
102
46
|
JSONPath expressions for repeated queries against the same JSON data.
|
|
103
47
|
"""
|
|
48
|
+
|
|
104
49
|
def __init__(self) -> None:
|
|
105
50
|
self._parser = RustSimpleJsonPath()
|
|
106
51
|
|
|
107
|
-
|
|
108
52
|
def child(self) -> Self:
|
|
109
53
|
"""Spawns a child instance of the class.
|
|
110
54
|
|
|
@@ -112,16 +56,16 @@ class JsonPath:
|
|
|
112
56
|
to set_data() need to be called on it for it to function.
|
|
113
57
|
|
|
114
58
|
It does however retain shared mutable access to the parent's collection
|
|
115
|
-
of pre-parsed path objects across all spawned children.
|
|
59
|
+
of pre-parsed path objects across all spawned children.
|
|
116
60
|
|
|
117
61
|
This is useful for the pattern of:
|
|
118
62
|
1. Searching a document for a path query.
|
|
119
63
|
2. Then using those results returned as the basis of a new 'root element'
|
|
120
64
|
for 'deeper' searches into a document.
|
|
121
|
-
|
|
122
|
-
Instead of assigning the original query results to current instance, it can
|
|
65
|
+
|
|
66
|
+
Instead of assigning the original query results to current instance, it can
|
|
123
67
|
be beneficial to spawn a child for each result, and assign the result
|
|
124
|
-
data to the child or multiple children if more than one result was returned.
|
|
68
|
+
data to the child or multiple children if more than one result was returned.
|
|
125
69
|
|
|
126
70
|
With this pattern the 'base' parent object will automatically contain
|
|
127
71
|
all parsed paths for the document that were searched by
|
|
@@ -134,10 +78,10 @@ class JsonPath:
|
|
|
134
78
|
child_cls = self.__class__
|
|
135
79
|
child = child_cls()
|
|
136
80
|
return child
|
|
137
|
-
|
|
81
|
+
|
|
138
82
|
def has_data(self) -> bool:
|
|
139
83
|
"""Returns True if this instance has data set to it, False otherwise."""
|
|
140
|
-
return self._parser.has_data()
|
|
84
|
+
return self._parser.has_data()
|
|
141
85
|
|
|
142
86
|
def set_data(self, input_data: Union[dict[str, Any], list[Any]]) -> None:
|
|
143
87
|
"""Set the JSON data for the query engine from a Python object.
|
|
@@ -146,7 +90,7 @@ class JsonPath:
|
|
|
146
90
|
operations performed against this instance.
|
|
147
91
|
|
|
148
92
|
Calling the function consecutively will replace any existing 'set' data.
|
|
149
|
-
|
|
93
|
+
|
|
150
94
|
Args:
|
|
151
95
|
input_data: The JSON data to set, as a Python dictionary or list.
|
|
152
96
|
|
|
@@ -163,19 +107,21 @@ class JsonPath:
|
|
|
163
107
|
|
|
164
108
|
The path expression is first parsed, then executed against the data previously
|
|
165
109
|
'set'. Parsed path expressions are cached for efficient future use.
|
|
166
|
-
|
|
110
|
+
|
|
167
111
|
Args:
|
|
168
|
-
path: The JSONPath expression to evaluate.
|
|
112
|
+
path(str): The JSONPath expression to evaluate.
|
|
169
113
|
|
|
170
114
|
Returns:
|
|
171
|
-
A list of values that match the JSONPath expression.
|
|
115
|
+
list[Any]: A list of values that match the JSONPath expression.
|
|
172
116
|
|
|
173
117
|
Raises:
|
|
174
118
|
ValueError: If the JSONPath expression is invalid.
|
|
175
119
|
LookupError: If this is called before data has not been set to this object through 'set_data()'.
|
|
176
120
|
"""
|
|
177
121
|
if not self._parser.has_data():
|
|
178
|
-
raise LookupError(
|
|
122
|
+
raise LookupError(
|
|
123
|
+
"Data must be set through calling 'set_data()' before attempting a query"
|
|
124
|
+
)
|
|
179
125
|
return self._parser.find(path)
|
|
180
126
|
|
|
181
127
|
def find_located(self, path: str) -> list[LocatedNode]:
|
|
@@ -183,19 +129,20 @@ class JsonPath:
|
|
|
183
129
|
|
|
184
130
|
The path expression is first parsed, then executed against the data previously
|
|
185
131
|
'set'. Parsed path expressions are cached for efficient future use.
|
|
186
|
-
|
|
132
|
+
|
|
187
133
|
Args:
|
|
188
|
-
path: The JSONPath expression to evaluate.
|
|
134
|
+
path(str): The JSONPath expression to evaluate.
|
|
189
135
|
|
|
190
136
|
Returns:
|
|
191
|
-
A list of LocatedNode objects that match the JSONPath expression.
|
|
137
|
+
list[LocatedNode]: A list of 'LocatedNode' objects that match the JSONPath expression.
|
|
192
138
|
|
|
193
139
|
Raises:
|
|
194
140
|
ValueError: If the JSONPath expression is invalid.
|
|
195
141
|
LookupError: If this is called before data has not been set to this object through 'set_data()'.
|
|
196
142
|
"""
|
|
197
143
|
if not self._parser.has_data():
|
|
198
|
-
raise LookupError(
|
|
144
|
+
raise LookupError(
|
|
145
|
+
"Data must be set through calling 'set_data()' before attempting a query"
|
|
146
|
+
)
|
|
199
147
|
result = self._parser.find_located(path)
|
|
200
|
-
return [LocatedNode(
|
|
201
|
-
|
|
148
|
+
return [LocatedNode(path, node) for (path, node) in result]
|
|
@@ -1,23 +1,25 @@
|
|
|
1
|
+
//! Module that provides a Python interface for querying JSON data using JSONPath expressions, implemented in Rust for performance.
|
|
2
|
+
|
|
1
3
|
use pyo3::{
|
|
2
4
|
exceptions,
|
|
3
5
|
prelude::*,
|
|
4
|
-
types::{PyBool, PyDict, PyDictMethods, PyFloat,
|
|
6
|
+
types::{PyBool, PyDict, PyDictMethods, PyFloat, PyList, PyListMethods, PyString},
|
|
5
7
|
};
|
|
6
8
|
use serde_json_path::NormalizedPath;
|
|
7
|
-
// use simple_jsonpath::SimpleJsonPath;
|
|
8
9
|
|
|
9
10
|
/// A Python module for querying JSON data using JSONPath expressions.
|
|
10
11
|
#[pymodule]
|
|
11
12
|
#[pyo3(name = "_simple_jsonpath")]
|
|
12
13
|
mod simple_jsonpath {
|
|
13
14
|
use super::*;
|
|
14
|
-
use pyo3::types::PyBytes;
|
|
15
|
+
use pyo3::types::{PyBytes, PyInt, PySlice};
|
|
15
16
|
use serde_json::Value;
|
|
16
17
|
use serde_json_path::JsonPath;
|
|
17
18
|
use std::{
|
|
18
19
|
collections::HashMap,
|
|
19
20
|
sync::{Arc, Mutex},
|
|
20
21
|
};
|
|
22
|
+
use ustr::Ustr;
|
|
21
23
|
|
|
22
24
|
/// A parser object that can be reused for multiple queries on the same JSON data.
|
|
23
25
|
#[pyclass]
|
|
@@ -32,7 +34,7 @@ mod simple_jsonpath {
|
|
|
32
34
|
#[new]
|
|
33
35
|
pub fn new() -> PyResult<Self> {
|
|
34
36
|
Ok(Self {
|
|
35
|
-
inner: Arc::new(Mutex::new(HashMap::
|
|
37
|
+
inner: Arc::new(Mutex::new(HashMap::with_capacity(500))),
|
|
36
38
|
data: None,
|
|
37
39
|
})
|
|
38
40
|
}
|
|
@@ -53,6 +55,7 @@ mod simple_jsonpath {
|
|
|
53
55
|
})
|
|
54
56
|
}
|
|
55
57
|
|
|
58
|
+
/// Check if the parser has JSON data set for querying.
|
|
56
59
|
pub fn has_data(&self) -> bool {
|
|
57
60
|
self.data.is_some()
|
|
58
61
|
}
|
|
@@ -130,14 +133,147 @@ mod simple_jsonpath {
|
|
|
130
133
|
};
|
|
131
134
|
let pyresult = PyList::empty(py);
|
|
132
135
|
for item in &result {
|
|
133
|
-
pyresult.append(
|
|
134
|
-
|
|
135
|
-
|
|
136
|
+
pyresult.append((
|
|
137
|
+
Path::new(item.location()),
|
|
138
|
+
serialize_value(py, item.node())?,
|
|
139
|
+
))?;
|
|
136
140
|
}
|
|
137
141
|
|
|
138
142
|
Ok(pyresult)
|
|
139
143
|
}
|
|
140
144
|
}
|
|
145
|
+
/// An enum to represent either a string key or an integer index in a JSONPath segment.
|
|
146
|
+
#[derive(Clone, Copy)]
|
|
147
|
+
enum Index {
|
|
148
|
+
U(Ustr),
|
|
149
|
+
I(usize),
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
impl Index {
|
|
153
|
+
fn to_normalized_segment(&self) -> String {
|
|
154
|
+
match self {
|
|
155
|
+
Self::I(num) => format!("[{num}]"),
|
|
156
|
+
Self::U(u) => format!("['{}']", u.as_str()),
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
/// A struct to represent a JSONPath location as a sequence of indexes, which can be accessed from Python.
|
|
161
|
+
#[pyclass(sequence)]
|
|
162
|
+
struct Path {
|
|
163
|
+
indexes: Vec<Index>,
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
#[pymethods]
|
|
167
|
+
impl Path {
|
|
168
|
+
/// Return the segment of the path at the given index, where the index can be an integer (positive or negative).
|
|
169
|
+
/// If the index is out of range, raise an IndexError. If the index is a slice, raise a ValueError since slicing is not supported.
|
|
170
|
+
fn __getitem__<'py>(&self, index: Bound<'py, PyAny>) -> PyResult<Bound<'py, PyAny>> {
|
|
171
|
+
let py = index.py();
|
|
172
|
+
match index.cast::<PyInt>() {
|
|
173
|
+
Ok(index) => {
|
|
174
|
+
let mut i = index.extract::<isize>()?;
|
|
175
|
+
if i < 0 {
|
|
176
|
+
if i == -1 {
|
|
177
|
+
let last = self.indexes.last().unwrap();
|
|
178
|
+
match last {
|
|
179
|
+
Index::U(u) => Ok(PyString::new(py, u.as_str()).into_any()),
|
|
180
|
+
Index::I(i) => Ok(PyInt::new(py, i).into_any()),
|
|
181
|
+
}
|
|
182
|
+
} else {
|
|
183
|
+
let abs_index = i.unsigned_abs();
|
|
184
|
+
if abs_index > self.__len__() {
|
|
185
|
+
Err(exceptions::PyIndexError::new_err("Index out of Range"))
|
|
186
|
+
} else if abs_index == self.__len__() {
|
|
187
|
+
Ok(PyString::new(py, "$").into_any())
|
|
188
|
+
} else {
|
|
189
|
+
let found =
|
|
190
|
+
self.indexes.get(self.indexes.len() - abs_index).unwrap();
|
|
191
|
+
match found {
|
|
192
|
+
Index::U(u) => Ok(PyString::new(py, u.as_str()).into_any()),
|
|
193
|
+
Index::I(i) => Ok(PyInt::new(py, i).into_any()),
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
} else if i as usize == 0 {
|
|
198
|
+
Ok(PyString::new(py, "$").into_any())
|
|
199
|
+
} else {
|
|
200
|
+
i -= 1;
|
|
201
|
+
if let Some(i) = self.indexes.get(i as usize) {
|
|
202
|
+
match i {
|
|
203
|
+
Index::U(u) => Ok(PyString::new(py, u.as_str()).into_any()),
|
|
204
|
+
Index::I(i) => Ok(PyInt::new(py, i).into_any()),
|
|
205
|
+
}
|
|
206
|
+
} else {
|
|
207
|
+
Err(exceptions::PyIndexError::new_err("Index out of Range"))
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
Err(_) => match index.cast::<PySlice>() {
|
|
212
|
+
Ok(_) => Err(exceptions::PyValueError::new_err(
|
|
213
|
+
"Slicing operations are not supported",
|
|
214
|
+
)),
|
|
215
|
+
Err(e) => Err(e.into()),
|
|
216
|
+
},
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/// Return the number of segments in the path, which is one more than the number of indexes (to account for the root '$' segment).
|
|
221
|
+
fn __len__(&self) -> usize {
|
|
222
|
+
self.indexes.len() + 1
|
|
223
|
+
}
|
|
224
|
+
fn __repr__(&self) -> String {
|
|
225
|
+
if self.indexes.len() == 1 {
|
|
226
|
+
"$".to_string()
|
|
227
|
+
} else {
|
|
228
|
+
let mut string = "$".to_string();
|
|
229
|
+
string.extend(self.indexes[1..].iter().map(|i| match i {
|
|
230
|
+
Index::U(u) => format!("['{}']", u),
|
|
231
|
+
Index::I(num) => format!("[{num}]"),
|
|
232
|
+
}));
|
|
233
|
+
string
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
/// Return a string representation of the path, which is the same as the __repr__ method in this case.
|
|
237
|
+
fn __str__(&self) -> String {
|
|
238
|
+
if self.indexes.len() == 1 {
|
|
239
|
+
"$".to_string()
|
|
240
|
+
} else {
|
|
241
|
+
let mut string = "$".to_string();
|
|
242
|
+
string.extend(self.indexes.iter().map(|i| i.to_normalized_segment()));
|
|
243
|
+
string
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
/// Return a new Path object that represents the parent path of the current path, which is the same path without the last segment. If the current path is the root '$', return None.
|
|
247
|
+
fn parent_path(&self) -> Option<Path> {
|
|
248
|
+
if !self.indexes.is_empty() {
|
|
249
|
+
let mut path = Vec::with_capacity(self.__len__() - 1);
|
|
250
|
+
path.extend(
|
|
251
|
+
self.indexes[..self.indexes.len() - 1]
|
|
252
|
+
.iter()
|
|
253
|
+
.map(|index| *index),
|
|
254
|
+
);
|
|
255
|
+
Some(Path { indexes: path })
|
|
256
|
+
} else {
|
|
257
|
+
None
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
impl Path {
|
|
263
|
+
/// Create a new Path object from a NormalizedPath, which is a sequence of PathElements. Each PathElement can be either a Name (string key) or an Index (integer index), and we convert them into our Index enum to store in the Path struct.
|
|
264
|
+
fn new(location: &NormalizedPath) -> Self {
|
|
265
|
+
let ids: Vec<Index> = location
|
|
266
|
+
.iter()
|
|
267
|
+
.map(|item| match item {
|
|
268
|
+
serde_json_path::PathElement::Name(name) => Index::U(Ustr::from(name)),
|
|
269
|
+
serde_json_path::PathElement::Index(num) => Index::I(*num),
|
|
270
|
+
})
|
|
271
|
+
.collect();
|
|
272
|
+
Self { indexes: ids }
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/// Helper function to serialize a serde_json::Value into a corresponding Python object.
|
|
141
277
|
fn serialize_value<'a>(py: Python<'a>, value: &'a Value) -> PyResult<Bound<'a, PyAny>> {
|
|
142
278
|
match value {
|
|
143
279
|
Value::Null => Ok(py.None().into_bound(py)),
|
|
@@ -167,125 +303,14 @@ mod simple_jsonpath {
|
|
|
167
303
|
}
|
|
168
304
|
}
|
|
169
305
|
}
|
|
170
|
-
|
|
171
|
-
struct LocatedNode<'a> {
|
|
172
|
-
full_path: String,
|
|
173
|
-
path_components: Vec<(usize, usize)>,
|
|
174
|
-
node: &'a Value,
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
impl<'a> LocatedNode<'a> {
|
|
178
|
-
fn new(full_path: &NormalizedPath, node: &'a Value) -> Self {
|
|
179
|
-
let (full_path, path_components) = split_normalized_path_component_ranges(full_path);
|
|
180
|
-
LocatedNode {
|
|
181
|
-
full_path,
|
|
182
|
-
path_components,
|
|
183
|
-
node,
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
// Implement the conversion function
|
|
187
|
-
fn convert_to_py_any<'py>(self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
|
|
188
|
-
// Convert the inner value to a Python integer;
|
|
189
|
-
let dict = PyDict::new(py);
|
|
190
|
-
let mapping = dict.as_mapping();
|
|
191
|
-
mapping.set_item("full_path", self.full_path)?;
|
|
192
|
-
mapping.set_item("path_components", self.path_components)?;
|
|
193
|
-
mapping.set_item("node", serialize_value(py, self.node)?)?;
|
|
194
|
-
|
|
195
|
-
Ok(dict.into_any())
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
pub fn split_normalized_path_component_ranges(
|
|
200
|
-
path: &NormalizedPath,
|
|
201
|
-
) -> (String, Vec<(usize, usize)>) {
|
|
202
|
-
let path_str = path.to_string();
|
|
203
|
-
let chars = &path_str.as_str();
|
|
204
|
-
|
|
205
|
-
let mut ranges = Vec::with_capacity(path.len() + 1);
|
|
206
|
-
ranges.push((0, 1)); // Start with the root component '$'
|
|
207
|
-
|
|
208
|
-
#[derive(Debug)]
|
|
209
|
-
enum State {
|
|
210
|
-
Start,
|
|
211
|
-
Root,
|
|
212
|
-
InBracket,
|
|
213
|
-
InQuotedField,
|
|
214
|
-
InEscapedChar,
|
|
215
|
-
InIndex,
|
|
216
|
-
}
|
|
217
|
-
let mut num_start = None;
|
|
218
|
-
let mut start = None;
|
|
219
|
-
let mut state = State::Start;
|
|
220
|
-
for (i, c) in chars.char_indices() {
|
|
221
|
-
match state {
|
|
222
|
-
State::Start => {
|
|
223
|
-
if c == '$' {
|
|
224
|
-
state = State::Root;
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
State::Root => {
|
|
228
|
-
if c == '[' {
|
|
229
|
-
state = State::InBracket;
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
State::InBracket => {
|
|
233
|
-
if c == ']' {
|
|
234
|
-
state = State::Root;
|
|
235
|
-
} else if c == '\'' {
|
|
236
|
-
state = State::InQuotedField;
|
|
237
|
-
} else {
|
|
238
|
-
state = State::InIndex;
|
|
239
|
-
num_start = Some(i);
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
State::InQuotedField => match c {
|
|
243
|
-
'\\' => state = State::InEscapedChar,
|
|
244
|
-
'\'' => {
|
|
245
|
-
match start {
|
|
246
|
-
Some(_) => ranges.push((start.take().unwrap(), i)),
|
|
247
|
-
None => ranges.push((i + 1, i - 1)),
|
|
248
|
-
}
|
|
249
|
-
state = State::InBracket;
|
|
250
|
-
}
|
|
251
|
-
_ => match start {
|
|
252
|
-
Some(_) => {}
|
|
253
|
-
None => start = Some(i),
|
|
254
|
-
},
|
|
255
|
-
},
|
|
256
|
-
State::InEscapedChar => {
|
|
257
|
-
state = State::InQuotedField;
|
|
258
|
-
}
|
|
259
|
-
State::InIndex => {
|
|
260
|
-
if c == ']' {
|
|
261
|
-
if num_start.is_some() {
|
|
262
|
-
let num = chars[num_start.take().unwrap()..i]
|
|
263
|
-
.parse::<usize>()
|
|
264
|
-
.unwrap();
|
|
265
|
-
ranges.push((0, num));
|
|
266
|
-
}
|
|
267
|
-
state = State::Root;
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
(path_str, ranges)
|
|
274
|
-
}
|
|
275
306
|
}
|
|
276
307
|
|
|
277
|
-
/// A struct to hold the located nodes for serialization.
|
|
278
308
|
|
|
279
|
-
/// Splits a normalized JSONPath into byte ranges `(start, end)` per component.
|
|
280
|
-
///
|
|
281
|
-
/// Example: `$['items'][0]['name']` -> `[(0,1), (1,10), (10,13), (13,21)]`
|
|
282
309
|
|
|
283
310
|
#[cfg(test)]
|
|
284
311
|
mod tests {
|
|
285
312
|
// use super::simple_jsonpath::SimpleJsonPath;
|
|
286
|
-
use super::simple_jsonpath::split_normalized_path_component_ranges;
|
|
287
313
|
use rstest::rstest;
|
|
288
|
-
use serde_json::Value;
|
|
289
314
|
use serde_json_path::JsonPath;
|
|
290
315
|
|
|
291
316
|
#[rstest]
|
|
@@ -309,22 +334,22 @@ mod tests {
|
|
|
309
334
|
assert!(result.is_ok());
|
|
310
335
|
}
|
|
311
336
|
|
|
312
|
-
#[test]
|
|
313
|
-
fn split_normalized_path_into_component_ranges() {
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
337
|
+
// #[test]
|
|
338
|
+
// fn split_normalized_path_into_component_ranges() {
|
|
339
|
+
// let data: Value = serde_json::from_str(r#"{"items":[{"name":"a"}] }"#).unwrap();
|
|
340
|
+
// let path = JsonPath::parse("$.items[0].name").unwrap();
|
|
341
|
+
// let located = path.query_located(&data).all();
|
|
342
|
+
// let location = located.first().unwrap().location();
|
|
343
|
+
// // $['items'][0]['name']
|
|
344
|
+
// println!("Getting here");
|
|
345
|
+
// let ranges = split_normalized_path_component_ranges(&location);
|
|
346
|
+
// println!("Getting here 2");
|
|
347
|
+
// let expected = vec![(0, 1), (3, 7), (0, 0), (15, 18)];
|
|
348
|
+
// for range in &ranges.1 {
|
|
349
|
+
// println!("Component: {}", &location.to_string()[range.0..range.1]);
|
|
350
|
+
// }
|
|
351
|
+
// println!("Getting here 3");
|
|
327
352
|
|
|
328
|
-
|
|
329
|
-
}
|
|
353
|
+
// assert_eq!(ranges.1, expected);
|
|
354
|
+
// }
|
|
330
355
|
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|