Skip to main content

Simple, safe and dependency-free JSON utility library for automation frameworks and Python applications.

Project description

ouroboros_json

Simple, safe and dependency-free JSON utility library for Python.

ouroboros_json provides a thin, well-typed wrapper around Python's standard json module with pragmatic helpers for automation frameworks, test suites and small applications.


Features

Area Capabilities
I/O Load, write and safe-default reads with automatic parent-directory creation
Key / Value Add, extract, check and remove top-level keys from JSON files
Nested Access Dot-notation get, set, has and delete on arbitrarily deep dicts
Transform Deep merge, flatten / unflatten, filter / exclude, structural diff, recursive key sort
Comparison Structural subset comparison between JSON objects
Serialization String encode / decode, validity check, pretty-print
Matrix 2-D matrix read & write helpers
Introspection List keys (flat or nested), count keys, find values by predicate
Typed PEP 561 py.typed marker, JSONType and PathLike aliases
Zero deps Only Python stdlib

Installation

pip install ouroboros_json

Python Compatibility

Supported: Python 3.9 - 3.14


Architecture

ouroboros_json/
+-- __init__.py          # Public API exports + __version__
+-- _types.py            # JSONType, PathLike aliases + internal helpers
+-- _io.py               # IOMixin: load_json, write_json, load_or_default
+-- _keyvalue.py         # KeyValueMixin: add, extract, check, remove keys
+-- _nested.py           # NestedMixin: get/set/has/delete via dot-notation
+-- _transform.py        # TransformMixin: merge, flatten, compare, diff, sort, find
+-- _serialization.py    # SerializationMixin: to/from string, matrix, pretty-print
+-- base.py              # BaseJSON facade (composes all mixins)
+-- exceptions.py        # Full exception hierarchy
+-- py.typed             # PEP 561 typed marker
tests/
+-- conftest.py          # Shared fixtures
+-- test_io.py           # I/O tests
+-- test_keyvalue.py     # Key/value tests
+-- test_nested.py       # Nested access tests
+-- test_transform.py    # Transform tests
+-- test_serialization.py # Serialization tests
+-- test_base.py         # Facade / integration tests

Quick Start

from ouroboros_json import BaseJSON

# Load / write files
data = BaseJSON.load_json("config.json")
BaseJSON.write_json("output.json", {"status": "ok"})
cfg = BaseJSON.load_or_default("optional.json", default={})

# Add or update a key in a file
BaseJSON.add_value_to_json("./tmp/data.json", "env", "dev")

# Extract a value
branch = BaseJSON.extract_value_from_json("./tmp/data.json", "branch")

# Check if a value exists
has_token = BaseJSON.check_value_in_json("./tmp/data.json", value="abc123")

Nested Access (dot-notation)

cfg = {"db": {"host": "localhost", "port": 5432}}

# Get
host = BaseJSON.get_nested(cfg, "db.host")        # "localhost"
name = BaseJSON.get_nested(cfg, "db.name", None)   # None (default)

# Set (creates intermediates automatically)
d: dict = {}
BaseJSON.set_nested(d, "db.host", "localhost")
# d == {"db": {"host": "localhost"}}

# Check existence
BaseJSON.has_nested(cfg, "db.host")   # True
BaseJSON.has_nested(cfg, "db.name")   # False

# Delete
d = {"a": {"b": 1, "c": 2}}
BaseJSON.delete_nested(d, "a.b")
# d == {"a": {"c": 2}}

# Custom separator
BaseJSON.get_nested(cfg, "db/host", separator="/")

Deep Merge

base = {"db": {"host": "localhost", "port": 5432}}
over = {"db": {"port": 3306}, "debug": True}
merged = BaseJSON.deep_merge(base, over)
# {"db": {"host": "localhost", "port": 3306}, "debug": True}

Flatten / Unflatten

flat = BaseJSON.flatten({"a": {"b": 1, "c": {"d": 2}}})
# {"a.b": 1, "a.c.d": 2}

nested = BaseJSON.unflatten({"a.b": 1, "a.c.d": 2})
# {"a": {"b": 1, "c": {"d": 2}}}

Diff

a = {"x": 1, "y": 2, "z": 3}
b = {"x": 1, "y": 99, "w": 4}
result = BaseJSON.diff(a, b)
# {"added": ["w"], "removed": ["z"], "changed": ["y"]}

Filter / Exclude / Remove

BaseJSON.filter_keys({"a": 1, "b": 2, "c": 3}, ["a", "c"])   # {"a": 1, "c": 3}
BaseJSON.exclude_keys({"a": 1, "b": 2, "c": 3}, ["b"])        # {"a": 1, "c": 3}
BaseJSON.remove_key({"a": 1, "b": 2}, "b")                     # {"a": 1}
BaseJSON.remove_keys({"a": 1, "b": 2, "c": 3}, ["b", "c"])     # {"a": 1}

Sort Keys Recursively

BaseJSON.sort_keys_deep({"b": 2, "a": {"d": 4, "c": 3}})
# {"a": {"c": 3, "d": 4}, "b": 2}

Find Values

d = {"a": 1, "b": {"c": "hello", "d": 42}}
BaseJSON.find_values(d, lambda v: isinstance(v, int))
# {"a": 1, "b.d": 42}

Structural Subset Comparison

json1 = {"a": 1, "b": {"c": 2, "d": [1, 2, 3]}}
json2 = {"b": {"d": [2]}}
assert BaseJSON.are_json_equal(json1, json2)  # True (json2 is a subset of json1)

Serialization Helpers

s = BaseJSON.to_string({"ok": True})            # '{"ok": true}'
obj = BaseJSON.from_string('{"ok": true}')      # {"ok": True}

# Validity check
BaseJSON.is_valid_json('{"a": 1}')  # True
BaseJSON.is_valid_json('{bad}')     # False

# Pretty-print
print(BaseJSON.pretty_print({"b": 2, "a": 1}, sort_keys=True))

Introspection

d = {"a": 1, "b": {"c": 2, "d": 3}}

BaseJSON.list_keys(d)                # ["a", "b"]
BaseJSON.list_keys(d, nested=True)   # ["a", "b.c", "b.d"]

BaseJSON.count_keys(d)               # 3 (leaf keys)
BaseJSON.count_keys(d, nested=False) # 2 (top-level)

Matrix Helpers

matrix = [[1, 2], [3, 4]]
BaseJSON.save_matrix_to_json(matrix, "./tmp/matrix.json")
loaded = BaseJSON.load_matrix_from_json("./tmp/matrix.json")
assert loaded == matrix

Exception Hierarchy

JSONError
+-- JSONFileNotFoundError
+-- JSONDecodeError
+-- JSONSerializationError
+-- JSONKeyError
+-- JSONPathError
+-- JSONMergeError
+-- JSONValidationError

All exceptions inherit from JSONError for convenient broad catches.


API Reference

BaseJSON - I/O

Method Description
load_json(path) Read and parse a JSON file
load_or_default(path, default=None) Load JSON or return default if file missing
write_json(path, data, *, indent=4, ensure_ascii=False) Write data to a JSON file (creates parents)

BaseJSON - Key / Value

Method Description
add_value_to_json(file_path, attribute, value) Add or update a top-level key in a JSON file
extract_value_from_json(file_path, attribute) Get the value of a top-level key
check_value_in_json(file_path, value) Check if a value exists among JSON values
remove_key(data, key) Return a copy without key
remove_keys(data, keys) Return a copy without the specified keys

BaseJSON - Nested Access

Method Description
get_nested(data, path, default=SENTINEL, *, separator=".") Get value via dot-notation
set_nested(data, path, value, *, separator=".") Set value via dot-notation (creates intermediates)
has_nested(data, path, *, separator=".") Check if a dot-notation path exists
delete_nested(data, path, *, separator=".") Delete a key at a dot-notation path

BaseJSON - Transform

Method Description
deep_merge(base, override) Recursively merge two dicts (override wins)
flatten(data, *, separator=".") Flatten nested dict to dotted keys
unflatten(data, *, separator=".") Reconstruct nested dict from flat keys
filter_keys(data, keys) Keep only specified keys
exclude_keys(data, keys) Remove specified keys
are_json_equal(json1, json2) Structural subset comparison
list_keys(data, *, nested=False, separator=".") List keys (top-level or all leaf paths)
count_keys(data, *, nested=True) Count keys
diff(original, modified) Compute added/removed/changed keys
sort_keys_deep(data) Recursively sort all dict keys
find_values(data, predicate) Find leaf values matching a predicate

BaseJSON - Serialization

Method Description
to_string(data, *, indent=None, ensure_ascii=False, sort_keys=False) Serialize to JSON string
from_string(text) Parse a JSON string
is_valid_json(text) Check if string is valid JSON
pretty_print(data, *, indent=2, sort_keys=False) Human-readable formatted JSON
save_matrix_to_json(matrix, file_path) Save 2-D matrix to JSON file
load_matrix_from_json(file_path) Load 2-D matrix from JSON file

Type Aliases

Name Definition
JSONType Union[Dict[str, Any], List[Any], str, int, float, bool, None]
PathLike Union[str, Path]

Development

pip install -e ".[dev]"

# Run tests
python -m pytest tests/ -v

# Lint
ruff check .

# Type check
mypy ouroboros_json/

License

This project is licensed under the MIT License - see the LICENSE file for details.


Authors

Flavio Brandolini

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

ouroboros_json-1.0.0.tar.gz (21.9 kB view details)

Uploaded Source

Built Distribution

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

ouroboros_json-1.0.0-py3-none-any.whl (18.9 kB view details)

Uploaded Python 3

File details

Details for the file ouroboros_json-1.0.0.tar.gz.

File metadata

  • Download URL: ouroboros_json-1.0.0.tar.gz
  • Upload date:
  • Size: 21.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.26 {"installer":{"name":"uv","version":"0.11.26","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for ouroboros_json-1.0.0.tar.gz
Algorithm Hash digest
SHA256 4dd186c876f4f364f5888e8867233fbf3205cdfa75db5567027e4d99488aec2e
MD5 5c2160494c193b0f116f1036fd6e0bef
BLAKE2b-256 53704104c8b8bfe3e53f0aca039de0934737eea761a28f59d0e534fc8c3b52e6

See more details on using hashes here.

File details

Details for the file ouroboros_json-1.0.0-py3-none-any.whl.

File metadata

  • Download URL: ouroboros_json-1.0.0-py3-none-any.whl
  • Upload date:
  • Size: 18.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.26 {"installer":{"name":"uv","version":"0.11.26","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for ouroboros_json-1.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 0d301e37d5f2c59515d532a53254ac69ffab12675e315d112b9be75d439db12a
MD5 506441b462ee11081b9acb36a46e70c1
BLAKE2b-256 f4a6adf2a3f62ac2835a699df753bd7a48e839dd2a73def7726a348bdc509a3a

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