|
18 | 18 | from typing import Tuple |
19 | 19 | from typing import Union |
20 | 20 |
|
21 | | -from .exceptions import JSONPointerKeyError |
22 | | -from .patch import JSONPatch |
23 | | - |
24 | 21 | if TYPE_CHECKING: |
25 | 22 | from jsonpath import CompoundJSONPath |
26 | 23 | from jsonpath import JSONPath |
@@ -222,73 +219,55 @@ def _select( |
222 | 219 | expressions: Tuple[Union[str, JSONPath, CompoundJSONPath], ...], |
223 | 220 | projection: Projection, |
224 | 221 | ) -> 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): |
232 | 223 | return None |
233 | 224 |
|
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 = {} |
236 | 244 | for expr in expressions: |
237 | 245 | 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) |
239 | 248 |
|
240 | | - return _fix_sparse_arrays(patch.apply(obj)) |
| 249 | + return _fix_sparse_arrays(obj) |
241 | 250 |
|
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) |
277 | 251 |
|
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 |
292 | 271 |
|
293 | 272 |
|
294 | 273 | def _fix_sparse_arrays(obj: Any) -> object: |
|
0 commit comments