Skip to content

Commit dc07778

Browse files
authored
Merge pull request #65 from jg-rp/better-projection
Simplify and speed up query projection
2 parents b5da1d4 + a9afd06 commit dc07778

File tree

1 file changed

+42
-63
lines changed

1 file changed

+42
-63
lines changed

jsonpath/fluent_api.py

Lines changed: 42 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,6 @@
1818
from typing import Tuple
1919
from typing import Union
2020

21-
from .exceptions import JSONPointerKeyError
22-
from .patch import JSONPatch
23-
2421
if TYPE_CHECKING:
2522
from jsonpath import CompoundJSONPath
2623
from jsonpath import JSONPath
@@ -222,73 +219,55 @@ def _select(
222219
expressions: Tuple[Union[str, JSONPath, CompoundJSONPath], ...],
223220
projection: Projection,
224221
) -> object:
225-
if isinstance(match.obj, str):
226-
return None
227-
if isinstance(match.obj, Sequence) or projection == Projection.FLAT:
228-
obj: Union[List[Any], Dict[str, Any]] = []
229-
elif isinstance(match.obj, Mapping):
230-
obj = {}
231-
else:
222+
if not isinstance(match.obj, (Mapping, Sequence)) or isinstance(match.obj, str):
232223
return None
233224

234-
patch = JSONPatch()
235-
225+
if projection == Projection.RELATIVE:
226+
obj: Dict[Union[int, str], Any] = {}
227+
for expr in expressions:
228+
path = self._env.compile(expr) if isinstance(expr, str) else expr
229+
for rel_match in path.finditer(match.obj): # type: ignore
230+
_patch_obj(rel_match.parts, obj, rel_match.obj)
231+
232+
return _fix_sparse_arrays(obj)
233+
234+
if projection == Projection.FLAT:
235+
arr: List[object] = []
236+
for expr in expressions:
237+
path = self._env.compile(expr) if isinstance(expr, str) else expr
238+
for rel_match in path.finditer(match.obj): # type: ignore
239+
arr.append(rel_match.obj)
240+
return arr
241+
242+
# Project from the root document
243+
obj = {}
236244
for expr in expressions:
237245
path = self._env.compile(expr) if isinstance(expr, str) else expr
238-
self._patch(match, path, patch, projection)
246+
for rel_match in path.finditer(match.obj): # type: ignore
247+
_patch_obj(match.parts + rel_match.parts, obj, rel_match.obj)
239248

240-
return _fix_sparse_arrays(patch.apply(obj))
249+
return _fix_sparse_arrays(obj)
241250

242-
def _patch(
243-
self,
244-
match: JSONPathMatch,
245-
path: Union[JSONPath, CompoundJSONPath],
246-
patch: JSONPatch,
247-
projection: Projection,
248-
) -> None:
249-
root_pointer = match.pointer()
250-
251-
for rel_match in path.finditer(match.obj): # type: ignore
252-
if projection == Projection.FLAT:
253-
patch.addap("/-", rel_match.obj)
254-
elif projection == Projection.ROOT:
255-
# Pointer string without a leading slash
256-
rel_pointer = "/".join(
257-
str(p).replace("~", "~0").replace("/", "~1")
258-
for p in rel_match.parts
259-
)
260-
pointer = root_pointer / rel_pointer
261-
_patch_parents(pointer.parent(), patch, match.root)
262-
patch.addap(pointer, rel_match.obj)
263-
else:
264-
# Natural projection
265-
pointer = rel_match.pointer()
266-
_patch_parents(pointer.parent(), patch, match.obj) # type: ignore
267-
patch.addap(pointer, rel_match.obj)
268-
269-
270-
def _patch_parents(
271-
pointer: JSONPointer,
272-
patch: JSONPatch,
273-
obj: Union[Sequence[Any], Mapping[str, Any]],
274-
) -> None:
275-
if pointer.parent().parts:
276-
_patch_parents(pointer.parent(), patch, obj)
277251

278-
if pointer.parts:
279-
try:
280-
_obj = pointer.resolve(obj)
281-
except JSONPointerKeyError:
282-
_obj = obj
283-
284-
# For lack of a better idea, we're patching arrays to dictionaries with
285-
# integer keys. This is to handle sparse array selections without having
286-
# to keep track of indexes and how they map from the root JSON value to
287-
# the selected JSON value.
288-
#
289-
# We'll fix these "sparse arrays" after the patch has been applied.
290-
if isinstance(_obj, (Sequence, Mapping)) and not isinstance(_obj, str):
291-
patch.addne(pointer, {})
252+
def _patch_obj(
253+
parts: Tuple[Union[int, str], ...],
254+
obj: Mapping[Union[str, int], Any],
255+
value: object,
256+
) -> None:
257+
_obj = obj
258+
259+
# For lack of a better idea, we're patching arrays to dictionaries with
260+
# integer keys. This is to handle sparse array selections without having
261+
# to keep track of indexes and how they map from the root JSON value to
262+
# the selected JSON value.
263+
#
264+
# We'll fix these "sparse arrays" after the patch has been applied.
265+
for part in parts[:-1]:
266+
if part not in _obj:
267+
_obj[part] = {} # type: ignore
268+
_obj = _obj[part]
269+
270+
_obj[parts[-1]] = value # type: ignore
292271

293272

294273
def _fix_sparse_arrays(obj: Any) -> object:

0 commit comments

Comments
 (0)