Skip to main content

A Python package for handling nested JSON structures.

Project description

🧭 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
⏭️ Ignore keys compare_files(ignore_keys=...) Skip specified keys during comparison
🔀 Ignore order compare_files(ignore_order=...) Treat lists as unordered sets during comparison
🧬 Deep merge merge_json Recursively merge two JSON objects with conflict strategies
🚨 Custom exceptions InvalidPathError, ElementNotFoundError, MergeConflictError 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,
    merge_json,
    MergeConflictError,
)

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

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

jsonavigator-1.3.0.tar.gz (20.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

jsonavigator-1.3.0-py3-none-any.whl (20.1 kB view details)

Uploaded Python 3

File details

Details for the file jsonavigator-1.3.0.tar.gz.

File metadata

  • Download URL: jsonavigator-1.3.0.tar.gz
  • Upload date:
  • Size: 20.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for jsonavigator-1.3.0.tar.gz
Algorithm Hash digest
SHA256 cfe2e40c9720e57e4a717c8a0869d7934ba6bb4eb66a94e88d66f509005266df
MD5 9dc345ba0c09ed289250223f82c7bd29
BLAKE2b-256 8085a23d60af7b38ce18e489036efdb6c412279c19087d7c851936b9a44be28c

See more details on using hashes here.

File details

Details for the file jsonavigator-1.3.0-py3-none-any.whl.

File metadata

  • Download URL: jsonavigator-1.3.0-py3-none-any.whl
  • Upload date:
  • Size: 20.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for jsonavigator-1.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 534c1f77a1b19658307ef4f21f88b73c4f62aa727d60b28ceecd87471a6222a3
MD5 26e09e7d95e4a44a5f2cdbfccf4c63a1
BLAKE2b-256 720b6144edef0ef1692a380fd1dc784f7603f943affed492ea3aedc1bbdede4a

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page