Skip to content

Nikhil-Singh-2503/JSONavigator

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

23 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

🧭 JSONavigator

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

PyPI Downloads PyPI version Python Versions License: MIT CI Repo Size


🤔 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

pip install jsonavigator

From source:

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

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

View Details

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

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.

traverse_json(data, separator="/")  # a/b[0], a/b[1], a/c

2️⃣ Get Value at a Specific Path

View Details
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

3️⃣ Set Value at a Specific Path

View Details

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

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]}}

4️⃣ Delete Value at a Specific Path

View Details

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

from jsoninja import delete_at_path

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

5️⃣ Flatten JSON

View Details

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

from jsoninja import flatten_json

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

6️⃣ Find a Value by Key

View Details

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

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)

7️⃣ Find All Paths for a Key

View Details

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

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"]

8️⃣ Empty All Values

View Details

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

from jsoninja import empty_all_the_values

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

9️⃣ Recursively Delete a Key

View Details

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

from jsoninja import delete_key

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

🔟 Validate & Format Paths

View Details
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]

1️⃣1️⃣ Compare Two JSON Structures

View Details

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

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:

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

🗂️ 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 anonymisationempty_all_the_values strips sensitive data for safe sharing.
  • 📥 ETL pipelines — flatten nested JSON before inserting into relational databases.

🔧 Development Setup

# 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.

# 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:
    git clone https://github.com/<your-username>/JSONavigator.git
  3. Create a feature branch:
    git checkout -b feature/my-feature
  4. Make changes and add tests for any new functionality.
  5. Run tests to make sure everything passes:
    pytest
  6. Commit and push your changes:
    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

  • set_value_at_path — update a value at a given path in place
  • 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 file for details.


👤 Author

Nikhil Singh


⭐ Star the Repo

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


Built with ❤️ using Python  |  PyPI  |  GitHub

About

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. It also allows you to compare 2 JSON files/objects.

Topics

Resources

License

Stars

Watchers

Forks

Contributors

Languages