Skip to main content

The lightweight Python library for safe, simple, dot-notation access to nested dicts and lists. Effortlessly get, set, and delete values deep in your complex JSON, API responses, and config files without verbose error-checking or handling KeyError exceptions.

Project description

Nested Utils

License Tests Python PyPI Documentation

The lightweight Python library for safe, simple, dot-notation access to nested dicts and lists. Effortlessly get, set, and delete values deep in your complex JSON, API responses, and config files without verbose error-checking or handling KeyError exceptions.

OG Image

Why nestedutils?

Working with deeply nested data (like JSON API responses) often leads to verbose, error-prone boilerplate:

# The Standard Way: Verbose and hard to read
user_name = None
if data and "users" in data and len(data["users"]) > 0:
    user = data["users"][0]
    if "profile" in user:
        user_name = user["profile"].get("name")

# With nestedutils: Clean, safe, and readable
from nestedutils import get_at

user_name = get_at(data, "users.0.profile.name")

Features

  • Simple Path Syntax: Use dot-notation strings ("a.b.c") or lists (["a", "b", "c"]) to navigate nested structures
  • Mixed Data Types: Seamlessly work with dictionaries, lists, and tuples (read-only for tuples)
  • List Index Support: Access list elements using numeric indices, including negative indices
  • Auto-creation: Automatically create missing intermediate containers when setting values
  • Flexible Fill Strategies: Control how missing containers are created with different fill strategies
  • Type Safety: Comprehensive error handling with descriptive error messages and error codes
  • Safety Limits: Built-in protection against excessive nesting (max depth: 100) and oversized lists (max index: 10,000)
  • Zero Dependencies: Pure Python implementation with no external dependencies

Use Cases

  • JSON API Responses: Safely extract values from complex, unpredictable JSON responses without dozens of checks.
  • Configuration Management: Easily read and modify deeply nested settings in configuration dictionaries.
  • Data Transformation: Rapidly remap data from one complex structure to another using get_at and set_at.

Installation

pip install nestedutils

Quick Start

from nestedutils import get_at, set_at, delete_at, exists_at

# Create a nested structure
data = {}

# Set values using dot-notation
set_at(data, "user.name", "John")
set_at(data, "user.age", 30)
set_at(data, "user.hobbies.0", "reading")
set_at(data, "user.hobbies.1", "coding")

# Access values
name = get_at(data, "user.name")  # "John"
age = get_at(data, "user.age")    # 30
first_hobby = get_at(data, "user.hobbies.0")  # "reading"

# Check if path exists
if exists_at(data, "user.name"):
    print("User name exists!")

# Delete values
delete_at(data, "user.age")

API Reference

get_at(data, path, default=None)

Retrieve a value from a nested data structure.

Parameters:

  • data: The data structure to navigate (dict, list, tuple, or nested combinations)
  • path: Path to the value (string with dot notation or list of keys/indices)
  • default: Value to return if path doesn't exist (default: None)

Returns: The value at the path, or default if not found

Examples:

data = {"a": {"b": {"c": 5}}}
get_at(data, "a.b.c")  # 5
get_at(data, "a.b.d", default=99)  # 99

data = {"items": [{"name": "apple"}, {"name": "banana"}]}
get_at(data, "items.1.name")  # "banana"
get_at(data, "items.-1.name")  # "banana" (negative index)

set_at(data, path, value, fill_strategy="auto")

Set a value in a nested data structure, creating intermediate containers as needed.

Parameters:

  • data: The data structure to modify (must be mutable: dict or list)
  • path: Path where to set the value (string with dot notation or list of keys/indices)
  • value: The value to set
  • fill_strategy: How to fill missing containers (default: "auto")
    • "auto": Intelligently creates {} for dict keys, [] for list indices, and None for sparse list gaps
    • "none": Fills missing list items with None
    • "dict": Always creates dictionaries
    • "list": Always creates lists

Note: Positive indices can extend lists (filling gaps as needed), but negative indices can only modify existing elements.

Examples:

data = {}
set_at(data, "user.profile.name", "Alice")
# Creates: {"user": {"profile": {"name": "Alice"}}}

data = {}
set_at(data, "items.0.name", "Item 1")
# Creates: {"items": [{"name": "Item 1"}]}

data = {}
set_at(data, "items.5", "Item 6", fill_strategy="none")
# Creates: {"items": [None, None, None, None, None, "Item 6"]}

data = [1, 2, 3]
set_at(data, "5", 99)  # Extends list with None gaps
# Creates: [1, 2, 3, None, None, 99]

data = [1, 2, 3]
set_at(data, "-1", 100)  # Updates existing last element
# Creates: [1, 2, 100]

exists_at(data, path)

Check if a path exists in a nested data structure.

Parameters:

  • data: The data structure to navigate (dict, list, tuple, or nested combinations)
  • path: Path to check (string with dot notation or list of keys/indices)

Returns: True if the path exists, False otherwise

Raises: PathError if the path format is invalid

Examples:

data = {"a": {"b": {"c": 5}}}
exists_at(data, "a.b.c")  # True
exists_at(data, "a.b.d")  # False

data = {"items": [{"name": "apple"}, {"name": "banana"}]}
exists_at(data, "items.1.name")  # True
exists_at(data, "items.5.name")  # False
exists_at(data, "items.-1.name")  # True (negative index)

delete_at(data, path, allow_list_mutation=False)

Delete a value from a nested data structure.

Parameters:

  • data: The data structure to modify
  • path: Path to the value to delete
  • allow_list_mutation: If True, allows deletion from lists (default: False)

Returns: The deleted value

Raises: PathError if the path doesn't exist or deletion is not allowed

Examples:

data = {"a": {"b": 1, "c": 2}}
delete_at(data, "a.b")  # Returns 1, data becomes {"a": {"c": 2}}

data = {"items": [1, 2, 3]}
delete_at(data, "items.1", allow_list_mutation=True)  # Returns 2
# data becomes {"items": [1, 3]}

Error Handling

The library uses PathError exceptions with error codes for different failure scenarios:

from nestedutils import PathError, PathErrorCode

try:
    set_at(data, "invalid.path", 1)
except PathError as e:
    print(e.message)  # Error message
    print(e.code)     # Error code (PathErrorCode enum)

Error Codes:

  • INVALID_PATH: Invalid path format or type
  • INVALID_INDEX: Invalid list index
  • MISSING_KEY: Key doesn't exist in dictionary
  • EMPTY_PATH: Path is empty
  • IMMUTABLE_CONTAINER: Attempted to modify a tuple
  • INVALID_FILL_STRATEGY: Invalid fill strategy value

Advanced Usage

Using List Paths

List paths are useful when keys contain dots:

data = {}
set_at(data, ["user.name", "first"], "John")
set_at(data, ["user.name", "last"], "Doe")
# Creates: {"user.name": {"first": "John", "last": "Doe"}}

Negative List Indices

Negative indices work like Python list indexing for reading and updating existing elements:

data = {"items": [10, 20, 30]}
get_at(data, "items.-1")  # 30 (last item)
set_at(data, "items.-1", 999)  # Updates last item (must exist)

Important: Negative indices can only reference existing elements. They cannot extend lists - attempting to use a negative index that's out of bounds will raise a PathError.

Working with Tuples

Tuples are read-only. You can read from them but cannot modify:

data = {"items": (1, 2, 3)}
get_at(data, "items.0")  # 1 (works)
set_at(data, "items.0", 9)  # Raises PathError (tuples are immutable)

Handling None Values

The library can navigate through None values when setting:

data = {"a": None}
set_at(data, "a.b.c", 10)
# Replaces None with container: {"a": {"b": {"c": 10}}}

Safety Limits

The library includes built-in safety limits to prevent excessive resource usage:

  • Maximum Path Depth: 100 levels (prevents deeply nested paths that could cause stack issues)
  • Maximum List Index: 10,000 (prevents creating extremely large sparse lists)

These limits help protect against accidental memory exhaustion or performance issues. If you hit these limits, you'll receive a PathError with a clear message.

Requirements

  • Python 3.8+

Changelog

See CHANGELOG.md for a detailed list of changes and version history.

License

MIT License - see LICENSE file for details.

Contributing

Contributions are welcome! Please read our Contributing Guide for details on our code of conduct, development setup, and the process for submitting pull requests.

Support

If you find this library useful, please consider:

  • Starring the repository on GitHub to help others discover it.
  • 💖 Sponsoring to support ongoing maintenance and development.

Become a Sponsor on GitHub | Support on Patreon

Links

Author

Y. Siva Sai Krishna

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

nestedutils-1.1.3.tar.gz (423.9 kB view details)

Uploaded Source

Built Distribution

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

nestedutils-1.1.3-py3-none-any.whl (12.7 kB view details)

Uploaded Python 3

File details

Details for the file nestedutils-1.1.3.tar.gz.

File metadata

  • Download URL: nestedutils-1.1.3.tar.gz
  • Upload date:
  • Size: 423.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.9.18 {"installer":{"name":"uv","version":"0.9.18","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for nestedutils-1.1.3.tar.gz
Algorithm Hash digest
SHA256 713400874f449bed4de1d9b6d366a2b3251e38dcabb400731f1b1cd0dea92c70
MD5 6f824f6be68f73a7cbb6cd01afd98d7e
BLAKE2b-256 fd30b8f465eba4f65533868ad34389479dc3d9e2498cdb615fa99bc40e04f60e

See more details on using hashes here.

File details

Details for the file nestedutils-1.1.3-py3-none-any.whl.

File metadata

  • Download URL: nestedutils-1.1.3-py3-none-any.whl
  • Upload date:
  • Size: 12.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.9.18 {"installer":{"name":"uv","version":"0.9.18","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for nestedutils-1.1.3-py3-none-any.whl
Algorithm Hash digest
SHA256 2a8f67f22058f3647eaa4936fecd7a229302554ebc00213dcd4c6e559691a0c0
MD5 8bd2400b773cf495ada56314964b658b
BLAKE2b-256 c46996051b438a3ca0864180c1a32c9d1b6c8f98be3168cc72e52a4301f2d4ee

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