From b3a7a7899baa8993605427af9a5f3b51108a1b3f Mon Sep 17 00:00:00 2001 From: unknown Date: Fri, 17 Oct 2025 10:21:32 +0530 Subject: [PATCH 1/6] added partial key --- src/jsonparse/parser.py | 41 ++++++++++++++++++++++++++++------------- 1 file changed, 28 insertions(+), 13 deletions(-) diff --git a/src/jsonparse/parser.py b/src/jsonparse/parser.py index 62815d2..80834ff 100755 --- a/src/jsonparse/parser.py +++ b/src/jsonparse/parser.py @@ -51,10 +51,13 @@ def __init__(self, stack_trace=False, queue_trace=False): self.stack_ref = self._stack_init() self.queue_ref = self._queue_init() - def find_key(self, data, key): + def find_key(self, data, key, partial, case_sensitive): # type: (Union[dict, list, OrderedDict], str) -> list if not self._valid_key_input(data, key): raise + + if partial and len(key)==0: + raise self.stack_ref = self._stack_init() # init a new queue every request self._stack_push(data) @@ -66,16 +69,19 @@ def find_key(self, data, key): elem = self._stack_pop() - if type(elem) is list: + if isinstance(elem,list): self._stack_push_list_elem(elem) elif isinstance(elem, (dict, OrderedDict)): - value = self._stack_all_key_values_in_dict(key, elem) + value = self._stack_all_key_values_in_dict(key, elem, partial, case_sensitive) if value: for v in value: - value_list.insert(0, v) + if isinstance(v,list): + value_list.extend([item for item in v]) + else: + value_list.insert(0, v) else: # according to RFC 7159, valid JSON can also contain a - # string, number, 'false', 'null', 'true' - pass # discard these other values as they don't have a key + # string, number, 'false', 'null', 'true' + pass # discard these other values as they don't have a key return value_list @@ -241,7 +247,7 @@ def _stack_push_list_elem(self, elem): self._stack_push(e) self._stack_trace() - def _stack_all_key_values_in_dict(self, key, elem): + def _stack_all_key_values_in_dict(self, key, elem, partial, case_sensitive): # type: (str, Union[dict, OrderedDict]) -> list value_list = [] @@ -253,12 +259,21 @@ def _stack_all_key_values_in_dict(self, key, elem): if len(elem) <= 0: # don't want an empty dict on the stack pass else: - for e in elem: - if e == key: - value_list.append(elem[e]) + value_list=[] + + for actual_key, value in elem.items(): + match_key = actual_key if case_sensitive else actual_key.lower() + search_key = key if case_sensitive else key.lower() + if partial: + if search_key in match_key: + value_list.append(value) else: - self._stack_push(elem[e]) - self._stack_trace() + if match_key == search_key: + value_list.append(value) + if not (partial and search_key in match_key) and not (not partial and match_key == search_key): + if isinstance(value, (dict, list, OrderedDict)): + self._stack_push(value) + self._stack_trace() return value_list def _stack_all_keys_values_in_dict(self, keys, elem): @@ -461,4 +476,4 @@ def _valid_value_input(self, data, value): raise TypeError elif not isinstance(value, (str, int, float, bool, type(None))): raise TypeError - return True + return True \ No newline at end of file From 8df408fb57622c0579ac1c610d6bf0410bcb49ac Mon Sep 17 00:00:00 2001 From: unknown Date: Sat, 18 Oct 2025 14:13:26 +0530 Subject: [PATCH 2/6] added test for the partial key --- tests/test_parser_partial.py | 151 +++++++++++++++++++++++++++++++++++ 1 file changed, 151 insertions(+) create mode 100644 tests/test_parser_partial.py diff --git a/tests/test_parser_partial.py b/tests/test_parser_partial.py new file mode 100644 index 0000000..c999c21 --- /dev/null +++ b/tests/test_parser_partial.py @@ -0,0 +1,151 @@ +from jsonparse.parser import Parser +import pytest + +class TestParserComplexJSON: + + @pytest.fixture + def parser(self): + + return Parser(stack_trace=False, queue_trace=False) + + @pytest.fixture + def complex_json(self): + + return [ + { + "id": "0001", + "type": "donut", + "exists": True, + "ppu": 0.55, + "batters": { + "batter": [ + {"id": "1001", "type": "Reg"}, + {"id": "1002", "type": "Chocolate"}, + {"id": "1003", "type": "Blueberry"}, + {"id": "1004", "type": "Devil's Food"}, + {"start": 5, "end": 8} + ] + }, + "topping": [ + {"id": "5001", "ty": "None"}, + {"id": "5002", "type": "Glazed"}, + {"id": "5003", "type": "Sugar"}, + {"id": "5004", "type": "Powdered Sugar"}, + {"id": "5005", "type": "Chocolate with Sprinkles"}, + {"id": "5006", "type": "Chocolate"}, + {"id": "5007", "type": "Maple"} + ], + "start": 22, + "end": 99 + }, + { + "id": "0002", + "type": "donut", + "exists": False, + "ppu": 42, + "batters": { + "batter": [{"id": "1001", "type": "Rul"}] + }, + "top_stuff": [ + {"id": "5001", "typ": "None"}, + {"id": "5002", "type": "Glazed"}, + {"id": "5003", "type": "Sugar"}, + {"id": "5004", "type": "Chocolate"}, + {"id": "5005", "type": "Maple"} + ], + "start": 1, + "end": 9 + }, + { + "id": "0003", + "type": "donut", + "exists": None, + "ppu": 7, + "batters": { + "batter": [ + {"id": "1001", "type": "Lar"}, + {"id": "1002", "type": "Chocolate"} + ] + }, + "on_top_thing": [ + {"id": "5001", "type": "None"}, + {"id": "5002", "type": "Glazed"}, + {"id": "5003", "type": "Chocolate"}, + {"id": "5004", "type": "Maple"} + ], + "start": 4, + "end": 7 + } + ] + + + def test_find_key_id(self, parser, complex_json): + """Find all 'id' keys.""" + result = parser.find_key(complex_json, "id", partial=False, case_sensitive=True) + assert result == [ + '1001', '1002', '1003', '1004', '5001', '5002', '5003', + '5004', '5005', '5006', '5007', '0001', '1001', '5001', + '5002', '5003', '5004', '5005', '0002', '1001', '1002', + '5001', '5002', '5003', '5004', '0003' + ] + + def test_find_key_type(self, parser, complex_json): + """Find all 'type' keys.""" + result = parser.find_key(complex_json, "type", partial=False, case_sensitive=True) + print(result) + assert result == [ + 'Reg', 'Chocolate', 'Blueberry', "Devil's Food", 'Glazed', 'Sugar', 'Powdered Sugar', + 'Chocolate with Sprinkles', 'Chocolate', 'Maple', 'donut', 'Rul', 'Glazed', 'Sugar', + 'Chocolate', 'Maple', 'donut', 'Lar', 'Chocolate', 'None', 'Glazed', 'Chocolate', 'Maple', 'donut' + ] + + def test_find_key_start(self, parser, complex_json): + """Find all 'start' keys.""" + result = parser.find_key(complex_json, "start", partial=False, case_sensitive=True) + assert result == [5, 22, 1, 4] + + def test_find_key_partial(self, parser, complex_json): + """Find keys with partial match 'ty' (should match 'type' and 'typ' and 'ty').""" + result = parser.find_key(complex_json, "ty", partial=True, case_sensitive=True) + assert result == [ + 'Reg', 'Chocolate', 'Blueberry', "Devil's Food", 'None', 'Glazed', 'Sugar', 'Powdered Sugar', + 'Chocolate with Sprinkles', 'Chocolate', 'Maple', 'donut', 'Rul', 'None', 'Glazed', + 'Sugar', 'Chocolate', 'Maple', 'donut', 'Lar', 'Chocolate', 'None', + 'Glazed', 'Chocolate', 'Maple', 'donut' + ] + + def test_find_key_case_insensitive(self, parser, complex_json): + """Find keys case-insensitively.""" + result = parser.find_key(complex_json, "TYPE", partial=False, case_sensitive=False) + assert all(isinstance(x,str) for x in result) + assert len(result)>10 + + def test_find_key_nonexistent(self, parser, complex_json): + """Try to find a non-existent key.""" + result = parser.find_key(complex_json, "unknown_key", partial=False, case_sensitive=True) + assert result == [] + + def test_invalid_data_type(self, parser, complex_json): + """Pass invalid data type.""" + try: + parser.find_key("invalid_data", "id", partial=False, case_sensitive=True) + except TypeError: + assert True + + def test_invalid_key_type(self, parser, complex_json): + """Pass non-string key.""" + try: + parser.find_key(complex_json, 1234, partial=False, case_sensitive=True) + except TypeError: + assert True + + + def test_empty_key(self, parser, complex_json): + """Pass empty key.""" + try: + parser.find_key(complex_json, "", partial=False, case_sensitive=True) + except ValueError: + assert True + + + From 31b19444604fcb5d6993bbfc1a737e05b3c619cd Mon Sep 17 00:00:00 2001 From: DagaBhai Date: Sat, 18 Oct 2025 14:17:46 +0530 Subject: [PATCH 3/6] Update test_parser_partial.py --- tests/test_parser_partial.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_parser_partial.py b/tests/test_parser_partial.py index c999c21..193e012 100644 --- a/tests/test_parser_partial.py +++ b/tests/test_parser_partial.py @@ -10,7 +10,6 @@ def parser(self): @pytest.fixture def complex_json(self): - return [ { "id": "0001", From 7e5a036f5bc4bff2634a1f3a7aba09e54d84c3fa Mon Sep 17 00:00:00 2001 From: DagaBhai Date: Sat, 18 Oct 2025 14:18:08 +0530 Subject: [PATCH 4/6] Update parser.py --- src/jsonparse/parser.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/jsonparse/parser.py b/src/jsonparse/parser.py index 80834ff..c62c592 100755 --- a/src/jsonparse/parser.py +++ b/src/jsonparse/parser.py @@ -55,7 +55,6 @@ def find_key(self, data, key, partial, case_sensitive): # type: (Union[dict, list, OrderedDict], str) -> list if not self._valid_key_input(data, key): raise - if partial and len(key)==0: raise @@ -476,4 +475,4 @@ def _valid_value_input(self, data, value): raise TypeError elif not isinstance(value, (str, int, float, bool, type(None))): raise TypeError - return True \ No newline at end of file + return True From 36a03335c850953c9789c060273c725358ca1014 Mon Sep 17 00:00:00 2001 From: DagaBhai Date: Sat, 18 Oct 2025 14:22:44 +0530 Subject: [PATCH 5/6] Update parser.py --- src/jsonparse/parser.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/jsonparse/parser.py b/src/jsonparse/parser.py index c62c592..7081c9c 100755 --- a/src/jsonparse/parser.py +++ b/src/jsonparse/parser.py @@ -259,7 +259,6 @@ def _stack_all_key_values_in_dict(self, key, elem, partial, case_sensitive): pass else: value_list=[] - for actual_key, value in elem.items(): match_key = actual_key if case_sensitive else actual_key.lower() search_key = key if case_sensitive else key.lower() @@ -278,7 +277,6 @@ def _stack_all_key_values_in_dict(self, key, elem, partial, case_sensitive): def _stack_all_keys_values_in_dict(self, keys, elem): # type: (list, Union[dict, OrderedDict]) -> list value_list = [] - if not isinstance(elem, (dict, OrderedDict)): raise TypeError elif type(keys) is not list: From 2e30b3423858382d91e1114d6812022d19836778 Mon Sep 17 00:00:00 2001 From: DagaBhai Date: Sat, 18 Oct 2025 14:22:58 +0530 Subject: [PATCH 6/6] Update test_parser_partial.py --- tests/test_parser_partial.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_parser_partial.py b/tests/test_parser_partial.py index 193e012..7548c09 100644 --- a/tests/test_parser_partial.py +++ b/tests/test_parser_partial.py @@ -137,7 +137,6 @@ def test_invalid_key_type(self, parser, complex_json): parser.find_key(complex_json, 1234, partial=False, case_sensitive=True) except TypeError: assert True - def test_empty_key(self, parser, complex_json): """Pass empty key."""