Metadata-Version: 2.1
Name: jsonavigator
Version: 1.2.0
Summary: A Python package for handling nested JSON structures.
Home-page: https://github.com/Nikhil-Singh-2503/JSONavigator
Author: Nikhil Singh
Author-email: nikhilraj7654@gmail.com
License: UNKNOWN
Platform: UNKNOWN
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 3
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Intended Audience :: Developers
Classifier: Topic :: Utilities
Classifier: Topic :: File Formats
Classifier: Topic :: File Formats :: JSON
Classifier: Topic :: Software Development :: Libraries
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.8
Description-Content-Type: text/markdown

<div align="center">

<h1>🧭 JSONavigator</h1>

<p><strong>The Swiss Army knife for nested JSON</strong> — traverse, flatten, search, validate, format, and diff JSON structures with a single lightweight Python library.</p>

[![PyPI Downloads](https://static.pepy.tech/badge/jsonavigator)](https://pepy.tech/projects/jsonavigator)
[![PyPI version](https://img.shields.io/pypi/v/jsonavigator?color=blue&logo=pypi&logoColor=white)](https://pypi.org/project/jsonavigator/)
[![Python Versions](https://img.shields.io/pypi/pyversions/jsonavigator?logo=python&logoColor=white)](https://pypi.org/project/jsonavigator/)
[![License: MIT](https://img.shields.io/pypi/l/jsonavigator?color=green)](https://github.com/Nikhil-Singh-2503/JSONavigator/blob/main/LICENSE)
[![CI](https://img.shields.io/github/actions/workflow/status/Nikhil-Singh-2503/JSONavigator/publish-to-pypi.yml?label=CI%2FCD&logo=github)](https://github.com/Nikhil-Singh-2503/JSONavigator/actions)
[![Repo Size](https://img.shields.io/github/repo-size/Nikhil-Singh-2503/Jsonavigator)](https://github.com/Nikhil-Singh-2503/JSONavigator)

</div>

---

## 🤔 Why JSONavigator?

Working with deeply nested JSON in Python is painful. Standard `dict` access breaks on missing keys; `jmespath` has a custom query language to learn; `pandas` is overkill for simple lookups.

**JSONavigator** solves this with a zero-dependency, pure-Python code that covers the whole lifecycle of nested JSON manipulation — from traversal to diffing — in a handful of intuitive functions.

```
4,000+ downloads  •  Zero dependencies  •  Python 3.8+  •  MIT Licensed
```

---

## ✨ Features

| Feature | Function | Description |
|---|---|---|
| 🔍 **Traverse** | `traverse_json` | Recursively yield every `(path, value)` pair |
| 📦 **Flatten** | `flatten_json` | Collapse nested JSON into a single-level dict |
| 🎯 **Get by path** | `get_value_at_path` | Retrieve a value using a dot-notation path |
| ✍️ **Set by path** | `set_value_at_path` | Update or set a value at a dot-notation path |
| 🗑️ **Delete by path** | `delete_at_path` | Remove a key/index at a dot-notation path |
| 🧹 **Delete key** | `delete_key` | Recursively remove every occurrence of a key |
| 🔎 **Find value** | `find_value_of_element` | Search for first occurrence of a key, anywhere |
| 🗺️ **Find all paths** | `find_all_paths_for_element` | Get every path where a key exists |
| 🧹 **Clear values** | `empty_all_the_values` | Reset all leaf values to `""` in-place |
| ✅ **Validate path** | `validate_path` | Assert a path string is well-formed |
| 💅 **Format path** | `format_path` | Pretty-print a path for human readability |
| 📊 **Compare JSON** | `compare_files` | Diff two JSON objects or files with a summary |
| 🚨 **Custom exceptions** | `InvalidPathError`, `ElementNotFoundError` | Semantic error handling |

---

## 📦 Installation

```bash
pip install jsonavigator
```

**From source:**

```bash
git clone https://github.com/Nikhil-Singh-2503/JSONavigator.git
cd JSONavigator
python -m venv venv && source venv/bin/activate
pip install -r requirements.txt
```

> **Requires:** Python ≥ 3.8. No runtime dependencies.

---

## ⚡ Quick Start

```python
from jsoninja import (
    traverse_json,
    get_value_at_path,
    flatten_json,
    find_value_of_element,
    find_all_paths_for_element,
    empty_all_the_values,
    validate_path,
    format_path,
    compare_files,
)

data = {
    "user": {
        "name": "Alice",
        "scores": [95, 87, 100],
        "address": {"city": "Berlin", "zip": "10115"}
    }
}

# Traverse every leaf
for path, value in traverse_json(data):
    print(f"{path}: {value}")
# user.name: Alice
# user.scores[0]: 95  ...

# Retrieve by path
print(get_value_at_path(data, "user.address.city"))  # Berlin

# Flatten to single level
flat = flatten_json(data)
# {"user.name": "Alice", "user.scores[0]": 95, ...}
```

---

## 📖 Usage Guide

### 1️⃣ Traverse Nested JSON
<details>
<summary>View Details</summary>

Yields `(path, value)` tuples for every leaf node. Supports mixed dicts and lists.

```python
from jsoninja import traverse_json

data = {"a": {"b": [1, 2], "c": 3}}

for path, value in traverse_json(data):
    print(f"Path: {path}, Value: {value}")
```

```
Path: a.b[0], Value: 1
Path: a.b[1], Value: 2
Path: a.c, Value: 3
```

> Use the `separator` parameter to change the default `.` delimiter.
>
> ```python
> traverse_json(data, separator="/")  # a/b[0], a/b[1], a/c
> ```

</details>

---

### 2️⃣ Get Value at a Specific Path
<details>
<summary>View Details</summary>

```python
from jsoninja import get_value_at_path

data = {"a": {"b": [10, 20, 30]}}

print(get_value_at_path(data, "a.b[2]"))   # 30
print(get_value_at_path(data, "a.b[0]"))   # 10
```

</details>

---

### 3️⃣ Set Value at a Specific Path
<details>
<summary>View Details</summary>

Update or create a leaf value using dot notation, modifying the dict in place.

```python
from jsoninja import set_value_at_path

data = {"user": {"scores": [10, 20]}}
set_value_at_path(data, "user.scores[1]", 99)
print(data)  # {"user": {"scores": [10, 99]}}
```

</details>

---

### 4️⃣ Delete Value at a Specific Path
<details>
<summary>View Details</summary>

Delete a key or pop an index at the specified path.

```python
from jsoninja import delete_at_path

data = {"a": {"b": 1, "c": 2}}
delete_at_path(data, "a.b")
print(data)  # {"a": {"c": 2}}
```

</details>

---

### 5️⃣ Flatten JSON
<details>
<summary>View Details</summary>

Collapses any depth of nesting into a flat `{path: value}` dictionary.

```python
from jsoninja import flatten_json

data = {"a": {"b": [1, 2], "c": 3}}
print(flatten_json(data))
```

```python
{
    "a.b[0]": 1,
    "a.b[1]": 2,
    "a.c": 3
}
```

</details>

---

### 6️⃣ Find a Value by Key
<details>
<summary>View Details</summary>

Returns the first occurrence of a key anywhere in the structure.

```python
from jsoninja import find_value_of_element

data = {"a": {"b": {"c": 42}}}
print(find_value_of_element("c", data))  # 42
print(find_value_of_element("z", data))  # "" (not found)
```

</details>

---

### 7️⃣ Find All Paths for a Key
<details>
<summary>View Details</summary>

Returns **every** path where the target key appears — great for duplicate-key analysis.

```python
from jsoninja import find_all_paths_for_element

data = {"a": 1, "b": {"a": 2}, "c": [{"a": 3}]}
print(find_all_paths_for_element(data, "a"))
# ["a", "b.a", "c[0].a"]
```

</details>

---

### 8️⃣ Empty All Values
<details>
<summary>View Details</summary>

Resets every leaf value to `""` — useful for creating JSON templates or anonymising data.

```python
from jsoninja import empty_all_the_values

data = {
    "a": 1,
    "b": {"c": 42, "d": [1, 2, {"e": "hello"}]},
}
print(empty_all_the_values(data))
```

```python
{
    "a": "",
    "b": {"c": "", "d": ["", "", {"e": ""}]}
}
```

</details>

---

### 9️⃣ Recursively Delete a Key
<details>
<summary>View Details</summary>

Recursively remove every occurrence of a key name anywhere in the nested object.

```python
from jsoninja import delete_key

data = {"id": 1, "user": {"id": 2, "name": "Alice"}}
delete_key(data, "id")
print(data)  # {"user": {"name": "Alice"}}
```

</details>

---

### 🔟 Validate & Format Paths
<details>
<summary>View Details</summary>

```python
from jsoninja import validate_path, format_path
from jsoninja.exceptions import InvalidPathError

# Validation
try:
    validate_path("a.b[1]")         # returns True
    validate_path("a.b[1")          # raises InvalidPathError (mismatched brackets)
except InvalidPathError as e:
    print(f"Error: {e}")

# Human-readable formatting
print(format_path("user.address.city"))   # user -> address -> city
print(format_path("a.b[1]"))             # a -> b[1]
```

</details>

---

### 1️⃣1️⃣ Compare Two JSON Structures
<details>
<summary>View Details</summary>

Diff two JSON objects or files and receive a structured list of changes plus a summary.

```python
from jsoninja import compare_files

json1 = {"a": {"b": 1, "c": 2}, "d": [1, 2]}
json2 = {"a": {"b": 1, "c": 99}, "d": [1, 2, 3], "e": "new"}

changes, summary = compare_files(json1, json2)

print(summary)
# {"added": 2, "removed": 0, "changed": 1, "type_changed": 0, "unknown": 0}

for change in changes:
    print(change)
# {"type": "changed",  "path": "a-->c", "old_value": 2,    "new_value": 99}
# {"type": "added",    "path": "d[2]",  "new_value": 3}
# {"type": "added",    "path": "e",     "new_value": "new"}
```

**Compare from file paths:**

```python
changes, summary = compare_files(
    r"path/to/old.json",
    r"path/to/new.json",
    isPath=True
)
```

> **Note:** When passing file paths as strings, use raw strings (`r"..."`) or double backslashes (`\\`) to avoid escape-sequence issues.

**Change types returned:**

| Type | Meaning |
|---|---|
| `added` | Key/value exists in JSON 2 but not JSON 1 |
| `removed` | Key/value exists in JSON 1 but not JSON 2 |
| `changed` | Same path, different value |
| `type_changed` | Same path, different Python type |

</details>

---

## 🗂️ Project Structure

```
JSONavigator/
├── jsoninja/
│   ├── __init__.py        # Public API surface
│   ├── core.py            # traverse, get, find, empty functions
│   ├── utils.py           # flatten, validate, format helpers
│   ├── compare.py         # JSON diff engine
│   └── exceptions.py      # Custom exception hierarchy
├── tests/
│   ├── test_core.py       # 30+ core function tests
│   ├── test_compare.py    # Compare module tests
│   ├── test_utils.py      # Utility function tests
│   └── conftest.py        # pytest fixtures
├── .github/workflows/
│   └── publish-to-pypi.yml  # Automated PyPI release CI
├── requirements.txt       # Dev/test dependencies
├── setup.py
└── README.md
```

---

## 🛠️ Package Reference

### `jsoninja.core`

| Function | Signature | Returns |
|---|---|---|
| `traverse_json` | `(data, parent_key='', separator='.')` | Generator of `(path, value)` |
| `get_value_at_path` | `(data, path, separator='.')` | Value at the path |
| `set_value_at_path` | `(data, path, new_value, separator='.')` | Mutated `data` |
| `delete_at_path` | `(data, path, separator='.')` | Mutated `data` |
| `delete_key` | `(data, target_key)` | Mutated `data` |
| `find_value_of_element` | `(target_key, data)` | First matched value or `""` |
| `find_all_paths_for_element` | `(file_data, target_key, path='', separator='.')` | `list[str]` of all paths |
| `empty_all_the_values` | `(data)` | Mutated `data` with `""` leaves, or `None` |

### `jsoninja.utils`

| Function | Signature | Returns |
|---|---|---|
| `flatten_json` | `(data, parent_key='', separator='.')` | Flat `dict` |
| `validate_path` | `(path, separator='.')` | `True` or raises `InvalidPathError` |
| `format_path` | `(path, separator='.')` | Human-readable path string |

### `jsoninja.compare`

| Function | Signature | Returns |
|---|---|---|
| `compare_files` | `(file1, file2, separator='-->', isPath=False)` | `(changes_list, summary_dict)` |

### `jsoninja.exceptions`

| Exception | When raised |
|---|---|
| `InvalidPathError` | Malformed path string passed to `validate_path` / `format_path` |
| `ElementNotFoundError` | Target element absent from JSON structure |
| `JSONStructureError` | Unexpected JSON structure type |

---

## 💡 Use Cases

- 🔬 **API response inspection** — quickly explore the shape of deeply nested API payloads.
- 🧪 **Test assertion helpers** — flatten and validate JSON responses in pytest fixtures.
- 📋 **Config file diffing** — compare application config files between environments.
- 🏗️ **JSON schema generation** — traverse and collect all paths to infer structure.
- 🕵️ **Data anonymisation** — `empty_all_the_values` strips sensitive data for safe sharing.
- 📥 **ETL pipelines** — flatten nested JSON before inserting into relational databases.

---

## 🔧 Development Setup

```bash
# 1. Clone the repository
git clone https://github.com/Nikhil-Singh-2503/JSONavigator.git
cd JSONavigator

# 2. Create and activate a virtual environment
python -m venv venv
source venv/bin/activate       # Windows: venv\Scripts\activate

# 3. Install development dependencies
pip install -r requirements.txt

# 4. Run the tests
pytest

# 5. Run with coverage report
pytest --cov=jsoninja --cov-report=term-missing
```

---

## 🧪 Testing

The test suite covers all public functions with 30+ unit tests using `pytest`.

```bash
# All tests
pytest

# Specific module
pytest tests/test_core.py
pytest tests/test_compare.py
pytest tests/test_utils.py

# With HTML coverage report
pytest --cov=jsoninja --cov-report=html
```

---

## 🤝 Contributing

Contributions are warmly welcome! Here's how to get started:

1. **Fork** the repository on GitHub.
2. **Clone** your fork:
   ```bash
   git clone https://github.com/<your-username>/JSONavigator.git
   ```
3. **Create** a feature branch:
   ```bash
   git checkout -b feature/my-feature
   ```
4. **Make changes** and add tests for any new functionality.
5. **Run tests** to make sure everything passes:
   ```bash
   pytest
   ```
6. **Commit** and **push** your changes:
   ```bash
   git commit -m "feat: add my-feature"
   git push origin feature/my-feature
   ```
7. Open a **Pull Request** against the `main` branch.

Please follow the existing code style and write docstrings for any new functions.

---

## 🚀 Roadmap

- [x] `set_value_at_path` — update a value at a given path in place
- [x] `delete_key` — remove a key anywhere in the structure
- [ ] JSONPath (`$`) query syntax support
- [ ] Async-friendly streaming traversal for large JSON files
- [ ] Type-annotated stubs (`py.typed` marker)
- [ ] `compare_files` — side-by-side HTML diff report

---

## 📄 License

This project is licensed under the **MIT License** — see the [LICENSE](https://github.com/Nikhil-Singh-2503/JSONavigator/blob/main/LICENSE) file for details.

---

## 👤 Author

**Nikhil Singh**

- 📧 Email: [nikhilraj7654@gmail.com](mailto:nikhilraj7654@gmail.com)
- 🐙 GitHub: [@Nikhil-Singh-2503](https://github.com/Nikhil-Singh-2503)
- 📦 PyPI: [jsonavigator](https://pypi.org/project/jsonavigator/)

---

## ⭐ Star the Repo

If JSONavigator saves you time, please consider giving it a ⭐ on GitHub — it helps others discover the project!

---

<div align="center">
  Built with ❤️ using Python &nbsp;|&nbsp; <a href="https://pypi.org/project/jsonavigator/">PyPI</a> &nbsp;|&nbsp; <a href="https://github.com/Nikhil-Singh-2503/JSONavigator">GitHub</a>
</div>


