Spaces:
Sleeping
Sleeping
| """ | |
| Nested JSON key-value extractor using jsonpath-ng. | |
| Walks any JSON-like structure (dict / list) and returns every value | |
| attached to a given key, regardless of how deeply nested it is. | |
| """ | |
| from __future__ import annotations | |
| from typing import Any | |
| from jsonpath_ng import parse | |
| from jsonpath_ng.exceptions import JsonPathLexerError, JsonPathParserError | |
| from logger import get_logger | |
| logger = get_logger(__name__) | |
| class Extractor: | |
| def __init__( | |
| self, | |
| data: dict | list, | |
| key_names: list[str], | |
| result_limit: int | None = 50, | |
| ): | |
| if not isinstance(key_names, list): | |
| raise TypeError("key_names must be a list") | |
| if not key_names: | |
| raise ValueError("key_names must be a non-empty list") | |
| for kn in key_names: | |
| if not isinstance(kn, str) or not kn: | |
| raise ValueError(f"each key_name must be a non-empty string, got {kn!r}") | |
| if result_limit is not None and result_limit < 1: | |
| raise ValueError("result_limit must be >= 1 or None") | |
| self._data = data | |
| self._key_names = key_names | |
| self._result_limit = result_limit | |
| def extract(self) -> dict[str, list[Any]]: | |
| return {k: self._extract_single(k) for k in self._key_names} | |
| def _extract_single(self, key_name: str) -> list[Any]: | |
| try: | |
| expr = _build_jp_expr(key_name) | |
| except ValueError: | |
| return [] | |
| vals = [m.value for m in expr.find(self._data)] | |
| if self._result_limit is not None: | |
| vals = vals[: self._result_limit] | |
| return vals | |
| def _build_jp_expr(key_name: str) -> Any: | |
| if '"' in key_name and "'" in key_name: | |
| raise ValueError(f"key contains both quote types: {key_name!r}") | |
| if '"' in key_name: | |
| expr_str = "$..['" + key_name + "']" | |
| else: | |
| expr_str = '$..["' + key_name + '"]' | |
| try: | |
| return parse(expr_str) | |
| except (JsonPathLexerError, JsonPathParserError): | |
| raise ValueError(f"unable to build jsonpath expression for key: {key_name!r}") | |
| def get_nested_values( | |
| data: dict | list, | |
| key_name: str, | |
| ) -> list[Any]: | |
| return Extractor(data, [key_name], result_limit=None).extract()[key_name] | |