|
16 | 16 |
|
17 | 17 | from .exceptions import JSONPathIndexError |
18 | 18 | from .exceptions import JSONPathTypeError |
| 19 | +from .match import NodeList |
19 | 20 | from .serialize import canonical_string |
20 | 21 |
|
21 | 22 | if TYPE_CHECKING: |
@@ -94,15 +95,7 @@ async def resolve_async(self, node: JSONPathMatch) -> AsyncIterable[JSONPathMatc |
94 | 95 |
|
95 | 96 |
|
96 | 97 | class IndexSelector(JSONPathSelector): |
97 | | - """Select an element from an array by index. |
98 | | -
|
99 | | - XXX: Change to make unquoted keys/properties a "singular path selector" |
100 | | - https://github.com/ietf-wg-jsonpath/draft-ietf-jsonpath-base/issues/522 |
101 | | -
|
102 | | - Considering we don't require mapping (JSON object) keys/properties to |
103 | | - be quoted, and that we support mappings with numeric keys, we also check |
104 | | - to see if the "index" is a mapping key, which is non-standard. |
105 | | - """ |
| 98 | + """Select an element from an array by index.""" |
106 | 99 |
|
107 | 100 | __slots__ = ("index", "_as_key") |
108 | 101 |
|
@@ -404,12 +397,87 @@ def __hash__(self) -> int: |
404 | 397 | return hash((self.query, self.token)) |
405 | 398 |
|
406 | 399 | def resolve(self, node: JSONPathMatch) -> Iterable[JSONPathMatch]: |
407 | | - # TODO: |
408 | | - raise Exception("not implemented") |
| 400 | + if isinstance(node.obj, Mapping): |
| 401 | + nodes = NodeList(self.query.finditer(node.root)) |
| 402 | + |
| 403 | + if nodes.empty(): |
| 404 | + return |
| 405 | + |
| 406 | + value = nodes[0].value |
| 407 | + |
| 408 | + if not isinstance(value, str): |
| 409 | + return |
| 410 | + |
| 411 | + with suppress(KeyError): |
| 412 | + match = node.new_child(self.env.getitem(node.obj, value), value) |
| 413 | + node.add_child(match) |
| 414 | + yield match |
| 415 | + |
| 416 | + if isinstance(node.obj, Sequence): |
| 417 | + nodes = NodeList(self.query.finditer(node.root)) |
| 418 | + |
| 419 | + if nodes.empty(): |
| 420 | + return |
| 421 | + |
| 422 | + value = nodes[0].value |
| 423 | + |
| 424 | + if not isinstance(value, int): |
| 425 | + return |
| 426 | + |
| 427 | + index = self._normalized_index(node.obj, value) |
| 428 | + |
| 429 | + with suppress(IndexError): |
| 430 | + match = node.new_child(self.env.getitem(node.obj, index), index) |
| 431 | + node.add_child(match) |
| 432 | + yield match |
409 | 433 |
|
410 | 434 | async def resolve_async(self, node: JSONPathMatch) -> AsyncIterable[JSONPathMatch]: |
411 | | - # TODO: |
412 | | - raise Exception("not implemented") |
| 435 | + if isinstance(node.obj, Mapping): |
| 436 | + nodes = NodeList( |
| 437 | + [match async for match in await self.query.finditer_async(node.root)] |
| 438 | + ) |
| 439 | + |
| 440 | + if nodes.empty(): |
| 441 | + return |
| 442 | + |
| 443 | + value = nodes[0].value |
| 444 | + |
| 445 | + if not isinstance(value, str): |
| 446 | + return |
| 447 | + |
| 448 | + with suppress(KeyError): |
| 449 | + match = node.new_child( |
| 450 | + await self.env.getitem_async(node.obj, value), value |
| 451 | + ) |
| 452 | + node.add_child(match) |
| 453 | + yield match |
| 454 | + |
| 455 | + if isinstance(node.obj, Sequence): |
| 456 | + nodes = NodeList( |
| 457 | + [match async for match in await self.query.finditer_async(node.root)] |
| 458 | + ) |
| 459 | + |
| 460 | + if nodes.empty(): |
| 461 | + return |
| 462 | + |
| 463 | + value = nodes[0].value |
| 464 | + |
| 465 | + if not isinstance(value, int): |
| 466 | + return |
| 467 | + |
| 468 | + index = self._normalized_index(node.obj, value) |
| 469 | + |
| 470 | + with suppress(IndexError): |
| 471 | + match = node.new_child( |
| 472 | + await self.env.getitem_async(node.obj, index), index |
| 473 | + ) |
| 474 | + node.add_child(match) |
| 475 | + yield match |
| 476 | + |
| 477 | + def _normalized_index(self, obj: Sequence[object], index: int) -> int: |
| 478 | + if index < 0 and len(obj) >= abs(index): |
| 479 | + return len(obj) + index |
| 480 | + return index |
413 | 481 |
|
414 | 482 |
|
415 | 483 | class Filter(JSONPathSelector): |
|
0 commit comments