jsonavigator 1.0.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Nikhil kumar
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,217 @@
1
+ Metadata-Version: 2.1
2
+ Name: jsonavigator
3
+ Version: 1.0.2
4
+ Summary: A Python package for handling nested JSON structures.
5
+ Home-page: https://github.com/Nikhil-Singh-2503/JSONavigator
6
+ Author: Nikhil Singh
7
+ Author-email: nikhilraj7654@gmail.com
8
+ License: UNKNOWN
9
+ Platform: UNKNOWN
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Operating System :: OS Independent
13
+ Requires-Python: >=3.8
14
+ Description-Content-Type: text/markdown
15
+
16
+
17
+ # JSONavigator
18
+
19
+ JSONavigator is a Python package designed to simplify working with nested JSON structures. It provides utilities for traversing, flattening, validating, and formatting JSON paths, making it easier to handle complex data structures.
20
+
21
+
22
+ ## **Features**
23
+ - **Traverse Nested JSON**: Recursively traverse dictionaries and lists to extract paths and values.
24
+ - **Flatten JSON**: Convert nested JSON into a single-level dictionary for easier access.
25
+ - **Validate Paths**: Ensure that JSON paths are properly formatted and valid.
26
+ - **Format Paths**: Improve readability of JSON paths by replacing separators with more user-friendly formats.
27
+ - **Custom Exceptions**: Handle errors gracefully with custom exception classes.
28
+ ## Installation
29
+
30
+ You can install `JSONavigator` using `pip`:
31
+
32
+ ```bash
33
+ pip install JSONavigator
34
+ ```
35
+ Alternatively, if you’re installing from source:
36
+
37
+ ```bash
38
+ git clone https://github.com/Nikhil-Singh-2503/JSONavigator.git
39
+ cd JSONavigator
40
+ ```
41
+ Create Virtual envirnoment:
42
+ ```bash
43
+ python -m venv venv
44
+ source venv/bin/activate
45
+ ```
46
+ Install the requirements:
47
+ ```bash
48
+ pip install -r requirements.txt
49
+ ```
50
+ ## Usage/Examples
51
+ Here’s how you can use the various features of JSONavigator:
52
+
53
+ **1. Traverse Nested JSON**
54
+
55
+ Use the `traverse_json` function to recursively traverse a nested JSON structure and extract paths and values.
56
+
57
+ ```python
58
+ from jsoninja.core import traverse_json
59
+
60
+ data = {"a": {"b": [1, 2], "c": 3}}
61
+
62
+ for path, value in traverse_json(data):
63
+ print(f"Path: {path}, Value: {value}")
64
+ ```
65
+
66
+ **Output**
67
+ ```
68
+ Path: a.b[0], Value: 1
69
+ Path: a.b[1], Value: 2
70
+ Path: a.c, Value: 3
71
+ ```
72
+
73
+ **2. Get Value at a Specific Path**
74
+
75
+ Use the `get_value_at_path` function to retrieve the value at a specific path in the JSON structure.
76
+
77
+ ```python
78
+ from jsoninja.core import get_value_at_path
79
+
80
+ data = {"a": {"b": [1, 2], "c": 3}}
81
+ value = get_value_at_path(data, "a.b[1]")
82
+ print(value) # Output: 2
83
+ ```
84
+
85
+ **Output**
86
+ `2`
87
+
88
+ **3. Flatten JSON**
89
+
90
+ Use the `flatten_json` function to convert a nested JSON structure into a single-level dictionary.
91
+
92
+ ```python
93
+ from jsoninja.utils import flatten_json
94
+
95
+ data = {"a": {"b": [1, 2], "c": 3}}
96
+ flattened = flatten_json(data)
97
+ print(flattened)
98
+ ```
99
+
100
+ **Output**
101
+ ```
102
+ {
103
+ "a.b[0]": 1,
104
+ "a.b[1]": 2,
105
+ "a.c": 3
106
+ }
107
+ ```
108
+ **4. Validate JSON Paths**
109
+
110
+ Use the `validate_path` function to ensure that a JSON path is properly formatted.
111
+
112
+ ```python
113
+ from jsoninja.utils import validate_path
114
+ from jsoninja.exceptions import InvalidPathError
115
+
116
+ try:
117
+ validate_path("a.b[1]")
118
+ except InvalidPathError as e:
119
+ print(f"Invalid path: {e}")
120
+ ```
121
+ **Output**
122
+ ```
123
+ True
124
+ ```
125
+ **5. Format JSON Paths**
126
+
127
+ Use the `format_path` function to make JSON paths more readable.
128
+
129
+ ```python
130
+ from jsoninja.utils import format_path
131
+
132
+ formatted_path = format_path("a.b[1]")
133
+ print(formatted_path)
134
+ ```
135
+ **Output**
136
+ ```
137
+ a -> b[1]
138
+ ```
139
+
140
+ ## **NOTE**
141
+
142
+ You can add your own seperator to each of the functions by passing value to a named variable `seperator`
143
+
144
+ **Example**
145
+
146
+ Suppose if you want to use seperator with `traverse_json` function.
147
+
148
+ ```python
149
+ from jsoninja.core import traverse_json
150
+
151
+ data = {"a": {"b": [1, 2], "c": 3}}
152
+
153
+ for path, value in traverse_json(data, seperator=*):
154
+ print(f"Path: {path}, Value: {value}")
155
+ ```
156
+
157
+ **Output**
158
+ ```
159
+ Path: a*b[0], Value: 1
160
+ Path: a*b[1], Value: 2
161
+ Path: a*c, Value: 3
162
+ ```
163
+ ## Contributing
164
+
165
+ Contributions to JSONavigator are welcome! To contribute:
166
+
167
+ - Fork the repository on GitHub.
168
+ - Clone your fork locally:
169
+ ```bash
170
+ git clone https://github.com/Nikhil-Singh-2503/JSONavigator.git
171
+ ```
172
+ - Create a new branch for your feature or bugfix:
173
+ ```bash
174
+ git checkout -b feature-name
175
+ ```
176
+ - Make your changes and write tests if applicable.
177
+
178
+ - Run the tests to ensure everything works:
179
+ ```bash
180
+ pytest
181
+ ```
182
+ - Commit your changes and push them to your fork:
183
+ ```bash
184
+ git commit -m "Add feature or fix"
185
+ git push origin feature-name
186
+ ```
187
+ - Open a pull request on the main repository.
188
+
189
+ ## Running Tests
190
+
191
+ To run the test suite, use `pytest`:
192
+
193
+ ```bash
194
+ pytest
195
+ ```
196
+ For coverage reports, install `pytest-cov` and run:
197
+ ```bash
198
+ pytest --cov=JSONavigator
199
+ ```
200
+ ## License
201
+ This project is licensed under the MIT License.
202
+
203
+
204
+
205
+ ## Contact
206
+
207
+ If you have any questions or need support, feel free to reach out:
208
+
209
+ - Email: nikhilraj7654@gmail.com
210
+
211
+ ## Acknowledgements
212
+
213
+ - Inspired by the need to simplify working with nested JSON structures.
214
+ - Built with ❤️ using Python.
215
+
216
+
217
+
@@ -0,0 +1,13 @@
1
+ jsoninja/__init__.py,sha256=AVQo9X-saXKdi1YoNb0pYUZJIq_IHtNXbVGaOJqJ8hk,635
2
+ jsoninja/core.py,sha256=2hK_NVmYXjM0-psFIv77804QYrwLmbqtzJQrul5wpNg,2499
3
+ jsoninja/exceptions.py,sha256=7LSPSLS3k9RAHwgNi-JYowj40-jWFScs5BOBj_w2QrU,819
4
+ jsoninja/utils.py,sha256=BRK1w6g9eRTX0OoQzd2cLHocpBTZIXmFCcH4KHkS00o,2232
5
+ tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
+ tests/conftest.py,sha256=iD_kJ_b0Pc4KQ_GfS4z5DXNRiZl7CvqSUHAeL8UrfqU,110
7
+ tests/test_core.py,sha256=sJuYQMfbL8NMMTvkmZrdhA_tWRb5H8nFt1RmN-imXiQ,635
8
+ tests/test_utils.py,sha256=53yNrzgTvPzv3hSLQG5QomxVdlVWBR1uy7QrSKhHifk,2151
9
+ jsonavigator-1.0.2.dist-info/LICENSE,sha256=rBTbB5VjyQKgcTg_Suv9XfcDan_uK5PHVtC3u8Rgiyw,1069
10
+ jsonavigator-1.0.2.dist-info/METADATA,sha256=hyoQGQG7Vv7lLI7vm65YwRajdsEr88HKKdSV2MusHxc,4880
11
+ jsonavigator-1.0.2.dist-info/WHEEL,sha256=tZoeGjtWxWRfdplE7E3d45VPlLNQnvbKiYnx7gwAy8A,92
12
+ jsonavigator-1.0.2.dist-info/top_level.txt,sha256=KGnFD0L7q0yqJDox7_CTXze5EuMxVofoR4OMVOBHGIU,15
13
+ jsonavigator-1.0.2.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: bdist_wheel (0.45.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ jsoninja
2
+ tests
jsoninja/__init__.py ADDED
@@ -0,0 +1,26 @@
1
+ # Import core functions from the core module
2
+ from jsoninja.core import (
3
+ traverse_json,
4
+ get_value_at_path,
5
+ find_all_paths_for_element
6
+ )
7
+
8
+ # Import custom exceptions from the exceptions module
9
+ from jsoninja.exceptions import (
10
+ InvalidPathError,
11
+ ElementNotFoundError
12
+ )
13
+
14
+ # Define what is exposed when someone uses `from nested_json_utils import *`
15
+ __all__ = [
16
+ "traverse_json",
17
+ "get_value_at_path",
18
+ "find_all_paths_for_element",
19
+ "InvalidPathError",
20
+ "ElementNotFoundError"
21
+ ]
22
+
23
+ # Optionally, define package metadata
24
+ __version__ = "0.1.0"
25
+ __author__ = "Nikhil Singh"
26
+ __email__ = "nikhilraj7654@gmail.com"
jsoninja/core.py ADDED
@@ -0,0 +1,66 @@
1
+ import re
2
+ def traverse_json(data, parent_key='', separator='.'):
3
+ """
4
+ Recursively traverse a nested JSON structure and yield paths and values.
5
+ :param data: The JSON data (dict or list).
6
+ :param parent_key: The accumulated key path.
7
+ :param separator: The separator to join keys.
8
+ :yield: (path, value) tuples.
9
+ """
10
+ if isinstance(data, dict):
11
+ for key, value in data.items():
12
+ new_key = f"{parent_key}{separator}{key}" if parent_key else key
13
+ yield from traverse_json(value, new_key, separator)
14
+ elif isinstance(data, list):
15
+ for index, value in enumerate(data):
16
+ new_key = f"{parent_key}[{index}]"
17
+ yield from traverse_json(value, new_key, separator)
18
+ else:
19
+ yield parent_key, data
20
+
21
+
22
+ def get_value_at_path(data, path, separator='.'):
23
+ """
24
+ Get the value at a given path in the JSON structure.
25
+ :param data: The JSON data (dict or list).
26
+ :param path: The path to the desired element.
27
+ :param separator: The separator used in the path.
28
+ :return: The value at the given path.
29
+ """
30
+ # Regular expression to match keys (e.g., "a") and indices (e.g., "[1]")
31
+ pattern = re.compile(rf'[^{re.escape(separator)}\[\]]+|\[\d+\]')
32
+
33
+ # Split the path into components
34
+ components = pattern.findall(path)
35
+ current = data
36
+
37
+ for component in components:
38
+ if component.startswith('[') and component.endswith(']'):
39
+ # Extract the index from the brackets
40
+ index = int(component[1:-1])
41
+ if isinstance(current, list):
42
+ current = current[index]
43
+ else:
44
+ raise KeyError(f"Index {index} accessed on non-list: {current}")
45
+ else:
46
+ # Access the key in the dictionary
47
+ if isinstance(current, dict):
48
+ current = current[component]
49
+ else:
50
+ raise KeyError(f"Key {component} accessed on non-dictionary: {current}")
51
+
52
+ return current
53
+
54
+ def find_all_paths_for_element(data, target, separator='.'):
55
+ """
56
+ Find all paths where the target element exists.
57
+ :param data: The JSON data (dict or list).
58
+ :param target: The target element to search for.
59
+ :param separator: The separator used in paths.
60
+ :return: List of paths where the target element is found.
61
+ """
62
+ paths = []
63
+ for path, value in traverse_json(data, separator=separator):
64
+ if value == target:
65
+ paths.append(path)
66
+ return paths
jsoninja/exceptions.py ADDED
@@ -0,0 +1,22 @@
1
+ class NestedJSONUtilsError(Exception):
2
+ """Base class for all exceptions in the nested_json_utils package."""
3
+ pass
4
+
5
+
6
+ class InvalidPathError(NestedJSONUtilsError):
7
+ """Raised when an invalid path is provided."""
8
+ def __init__(self, message="Invalid path provided."):
9
+ super().__init__(message)
10
+
11
+
12
+ class ElementNotFoundError(NestedJSONUtilsError):
13
+ """Raised when the target element is not found in the JSON structure."""
14
+ def __init__(self, element, message="Element not found in JSON structure."):
15
+ self.element = element
16
+ super().__init__(f"{message} Element: {element}")
17
+
18
+
19
+ class JSONStructureError(NestedJSONUtilsError):
20
+ """Raised when there is an issue with the JSON structure."""
21
+ def __init__(self, message="Invalid JSON structure."):
22
+ super().__init__(message)
jsoninja/utils.py ADDED
@@ -0,0 +1,63 @@
1
+ from .exceptions import InvalidPathError
2
+
3
+ def validate_path(path, separator='.'):
4
+ """
5
+ Validate if a given path is properly formatted.
6
+ :param path: The path to validate.
7
+ :param separator: The separator used in the path.
8
+ :raises InvalidPathError: If the path is invalid.
9
+ """
10
+ check = True
11
+ if not isinstance(path, str):
12
+ raise InvalidPathError("Path must be a string.")
13
+
14
+ # Check if the path contains only valid characters
15
+ valid_chars = set("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789[]_")
16
+ valid_chars.add(separator)
17
+ for char in path:
18
+ if char not in valid_chars:
19
+ check = False
20
+ raise InvalidPathError(f"Invalid character '{char}' in path.")
21
+
22
+ # Ensure brackets are properly closed if present
23
+ if '[' in path or ']' in path:
24
+ if path.count('[') != path.count(']'):
25
+ check = False
26
+ raise InvalidPathError("Mismatched brackets in path.")
27
+ return check
28
+
29
+ def format_path(path, separator='.'):
30
+ """
31
+ Format a path for better readability.
32
+ :param path: The path to format.
33
+ :param separator: The separator used in the path.
34
+ :return: A formatted version of the path.
35
+ """
36
+ if not isinstance(path, str):
37
+ raise InvalidPathError("Path must be a string.")
38
+
39
+ # Replace separators with a more readable format if needed
40
+ return path.replace(separator, " -> ")
41
+
42
+
43
+ def flatten_json(data, parent_key='', separator='.'):
44
+ """
45
+ Flatten a nested JSON structure into a single-level dictionary.
46
+ :param data: The JSON data (dict or list).
47
+ :param parent_key: The accumulated key path.
48
+ :param separator: The separator to join keys.
49
+ :return: A flattened dictionary.
50
+ """
51
+ items = {}
52
+ if isinstance(data, dict):
53
+ for key, value in data.items():
54
+ new_key = f"{parent_key}{separator}{key}" if parent_key else key
55
+ items.update(flatten_json(value, new_key, separator))
56
+ elif isinstance(data, list):
57
+ for index, value in enumerate(data):
58
+ new_key = f"{parent_key}[{index}]"
59
+ items.update(flatten_json(value, new_key, separator))
60
+ else:
61
+ items[parent_key] = data
62
+ return items
63
+
tests/__init__.py ADDED
File without changes
tests/conftest.py ADDED
@@ -0,0 +1,7 @@
1
+ # tests/conftest.py
2
+
3
+ import pytest
4
+
5
+ @pytest.fixture
6
+ def sample_json():
7
+ return {"a": {"b": [1, 2], "c": 3}}
tests/test_core.py ADDED
@@ -0,0 +1,20 @@
1
+ import pytest
2
+ from jsoninja.core import traverse_json, get_value_at_path, find_all_paths_for_element
3
+
4
+ def test_traverse_json():
5
+ data = {"a": {"b": [1, 2], "c": 3}}
6
+ expected = [
7
+ ("a.b[0]", 1),
8
+ ("a.b[1]", 2),
9
+ ("a.c", 3)
10
+ ]
11
+ assert list(traverse_json(data)) == expected
12
+
13
+ def test_get_value_at_path():
14
+ data = {"a": {"b": [1, 2], "c": 3}}
15
+ assert get_value_at_path(data, "a.b[1]") == 2
16
+ assert get_value_at_path(data, "a.c") == 3
17
+
18
+ def test_find_all_paths_for_element():
19
+ data = {"a": {"b": [1, 2], "c": 3}, "d": {"b": 2}}
20
+ assert find_all_paths_for_element(data, 2) == ["a.b[1]", "d.b"]
tests/test_utils.py ADDED
@@ -0,0 +1,89 @@
1
+ import pytest
2
+ from jsoninja.exceptions import InvalidPathError
3
+ from jsoninja.utils import (
4
+ validate_path,
5
+ format_path,
6
+ flatten_json,
7
+ )
8
+
9
+ # Test cases for validate_path
10
+ def test_validate_path_valid():
11
+ """
12
+ Test validating a properly formatted path.
13
+ """
14
+ assert validate_path("a.b[1]") == True
15
+
16
+
17
+ def test_validate_path_invalid_type():
18
+ """
19
+ Test validating an invalid path type (not a string).
20
+ """
21
+ with pytest.raises(InvalidPathError, match="Path must be a string."):
22
+ validate_path(123)==False # Path is not a string
23
+
24
+
25
+ def test_validate_path_invalid_characters():
26
+ """
27
+ Test validating a path with invalid characters.
28
+ """
29
+ with pytest.raises(InvalidPathError, match="Invalid character '#' in path."):
30
+ validate_path("a.b#[1]")==False
31
+
32
+
33
+ def test_validate_path_mismatched_brackets():
34
+ """
35
+ Test validating a path with mismatched brackets.
36
+ """
37
+ with pytest.raises(InvalidPathError, match="Mismatched brackets in path."):
38
+ validate_path("a.b[1")==False
39
+
40
+
41
+ # Test cases for format_path
42
+ def test_format_path():
43
+ """
44
+ Test formatting a path for better readability.
45
+ """
46
+ assert format_path("a.b[1]") == "a -> b[1]"
47
+
48
+
49
+ def test_format_path_invalid_type():
50
+ """
51
+ Test formatting an invalid path type (not a string).
52
+ """
53
+ with pytest.raises(InvalidPathError, match="Path must be a string."):
54
+ format_path(123) # Path is not a string
55
+
56
+
57
+ # Test cases for flatten_json
58
+ def test_flatten_json():
59
+ """
60
+ Test flattening a simple nested JSON structure.
61
+ """
62
+ data = {"a": {"b": [1, 2], "c": 3}}
63
+ expected = {
64
+ "a.b[0]": 1,
65
+ "a.b[1]": 2,
66
+ "a.c": 3
67
+ }
68
+ assert flatten_json(data) == expected
69
+
70
+
71
+ def test_flatten_json_empty():
72
+ """
73
+ Test flattening an empty dictionary.
74
+ """
75
+ data = {}
76
+ expected = {}
77
+ assert flatten_json(data) == expected
78
+
79
+
80
+ def test_flatten_json_nested_lists():
81
+ """
82
+ Test flattening a JSON structure with nested lists.
83
+ """
84
+ data = {"a": [{"b": 1}, {"c": 2}]}
85
+ expected = {
86
+ "a[0].b": 1,
87
+ "a[1].c": 2
88
+ }
89
+ assert flatten_json(data) == expected