{"id":35120392,"url":"https://github.com/ysskrishna/nestedutils","last_synced_at":"2026-02-14T09:07:59.240Z","repository":{"id":327854886,"uuid":"1111024339","full_name":"ysskrishna/nestedutils","owner":"ysskrishna","description":"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.","archived":false,"fork":false,"pushed_at":"2025-12-27T18:29:06.000Z","size":732,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-12-30T00:55:27.994Z","etag":null,"topics":["data-access","dot-notation","hacktoberfest","json","library","nested-structures","pypi","pypi-package","pytest","python","python-library","release-automation","utilities","uv","ysskrishna"],"latest_commit_sha":null,"homepage":"https://ysskrishna.github.io/nestedutils/","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ysskrishna.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null},"funding":{"github":"ysskrishna","patreon":"ysskrishna"}},"created_at":"2025-12-06T05:42:50.000Z","updated_at":"2025-12-27T18:28:55.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/ysskrishna/nestedutils","commit_stats":null,"previous_names":["ysskrishna/nestedutils"],"tags_count":8,"template":false,"template_full_name":null,"purl":"pkg:github/ysskrishna/nestedutils","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ysskrishna%2Fnestedutils","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ysskrishna%2Fnestedutils/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ysskrishna%2Fnestedutils/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ysskrishna%2Fnestedutils/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ysskrishna","download_url":"https://codeload.github.com/ysskrishna/nestedutils/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ysskrishna%2Fnestedutils/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28399685,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-13T14:36:09.778Z","status":"ssl_error","status_checked_at":"2026-01-13T14:35:19.697Z","response_time":56,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["data-access","dot-notation","hacktoberfest","json","library","nested-structures","pypi","pypi-package","pytest","python","python-library","release-automation","utilities","uv","ysskrishna"],"created_at":"2025-12-27T23:40:31.309Z","updated_at":"2026-02-14T09:07:59.233Z","avatar_url":"https://github.com/ysskrishna.png","language":"Python","readme":"# Nested Utils\n\n![License](https://img.shields.io/badge/license-MIT-blue.svg)\n![Tests](https://github.com/ysskrishna/nestedutils/actions/workflows/test.yml/badge.svg)\n![Python](https://img.shields.io/badge/python-3.8%2B-blue.svg)\n[![PyPI](https://img.shields.io/pypi/v/nestedutils)](https://pypi.org/project/nestedutils/)\n[![PyPI Downloads](https://static.pepy.tech/personalized-badge/nestedutils?period=total\u0026units=INTERNATIONAL_SYSTEM\u0026left_color=GREY\u0026right_color=BLUE\u0026left_text=downloads)](https://pepy.tech/projects/nestedutils)\n[![Documentation](https://img.shields.io/badge/docs-ysskrishna.github.io%2Fnestedutils-blue.svg)](https://ysskrishna.github.io/nestedutils/)\n[![Interactive Playground](https://img.shields.io/badge/demo-Try%20it%20now!-green.svg)](https://ysskrishna.github.io/nestedutils/playground/)\n\nThe 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.\n\n\u003e 🚀 **Try it interactively in your browser!** Test the library with our [Interactive Playground](https://ysskrishna.github.io/nestedutils/playground/) - no installation required.\n\n![OG Image](https://raw.githubusercontent.com/ysskrishna/nestedutils/main/media/og.png)\n\n## Why nestedutils?\n\nWorking with deeply nested data (like JSON API responses) often leads to verbose, error-prone boilerplate:\n\n```python\n# The Standard Way: Verbose and hard to read\nuser_name = None\nif data and \"users\" in data and len(data[\"users\"]) \u003e 0:\n    user = data[\"users\"][0]\n    if \"profile\" in user:\n        user_name = user[\"profile\"].get(\"name\")\n\n# With nestedutils: Clean, safe, and readable\nfrom nestedutils import get_at\n\nuser_name = get_at(data, \"users.0.profile.name\")\n```\n\n## Features\n\n- **Simple Path Syntax**: Use dot-notation strings (`\"a.b.c\"`) or lists (`[\"a\", \"b\", \"c\"]`) to navigate nested structures\n- **Mixed Data Types**: Seamlessly work with dictionaries, lists, and tuples (read-only for tuples)\n- **List Index Support**: Access list elements using numeric indices, including negative indices\n- **Auto-creation**: Automatically create missing intermediate containers when setting values (with `create=True`)\n- **Introspection**: Analyze nested structures with `get_depth`, `count_leaves`, and `get_all_paths`\n- **Type Safety**: Comprehensive error handling with descriptive error messages and error codes\n- **Safety Limits**: Built-in protection against excessive nesting (max depth: 100) and oversized lists (max index: 10,000)\n- **Zero Dependencies**: Pure Python implementation with no external dependencies\n\n## Use Cases\n\n- **JSON API Responses**: Safely extract values from complex, unpredictable JSON responses without dozens of checks.\n- **Configuration Management**: Easily read and modify deeply nested settings in configuration dictionaries.\n- **Data Transformation**: Rapidly remap data from one complex structure to another using `get_at` and `set_at`.\n\n## Terminology\n\n| Term | Definition |\n|------|------------|\n| **Path** | A navigation string or list that specifies a location in nested data (e.g., `\"user.profile.name\"` or `[\"user\", \"profile\", \"name\"]`) |\n| **Key** | An individual dictionary key used to access a value (e.g., `\"name\"`, `\"profile\"`) |\n| **Index** | A numeric position in a list or tuple (e.g., `0`, `-1` for last element) |\n\n## Installation\n\n```bash\npip install nestedutils\n```\n\n## Quick Start\n\n```python\nfrom nestedutils import get_at, set_at, delete_at, exists_at, get_depth, count_leaves, get_all_paths\n\n# Create a nested structure\ndata = {}\n\n# Set values using dot-notation\nset_at(data, \"user.name\", \"John\", create=True)\nset_at(data, \"user.age\", 30, create=True)\nset_at(data, \"user.hobbies.0\", \"reading\", create=True)\nset_at(data, \"user.hobbies.1\", \"coding\", create=True)\n\n# Access values\nname = get_at(data, \"user.name\")  # \"John\"\nage = get_at(data, \"user.age\")    # 30\nfirst_hobby = get_at(data, \"user.hobbies.0\")  # \"reading\"\n\n# Check if path exists\nif exists_at(data, \"user.name\"):\n    print(\"User name exists!\")\n\n# Delete values\ndelete_at(data, \"user.age\")\n```\n\n## API Reference\n\n### `get_at(data, path, *, default=None)`\n\nRetrieve a value from a nested data structure.\n\n**Parameters:**\n\n- `data`: The data structure to navigate (dict, list, tuple, or nested combinations)\n- `path`: Path to the value (string with dot notation or list of keys/indices)\n- `default`: Value to return if path doesn't exist (keyword-only parameter, default: `None`)\n\n**Returns:** The value at the path, or `default` if provided and path doesn't exist\n\n**Raises:** `PathError` if the path doesn't exist and `default` is not provided\n\n**Note:** By default, `get_at` raises `PathError` for missing paths. Use the `default` parameter for optional/nullable access.\n\n**Examples:**\n\n```python\ndata = {\"a\": {\"b\": {\"c\": 5}}}\nget_at(data, \"a.b.c\")  # 5\nget_at(data, \"a.b.d\")  # Raises PathError (path doesn't exist)\nget_at(data, \"a.b.d\", default=99)  # 99 (returns default)\n\ndata = {\"items\": [{\"name\": \"apple\"}, {\"name\": \"banana\"}]}\nget_at(data, \"items.1.name\")  # \"banana\"\nget_at(data, \"items.-1.name\")  # \"banana\" (negative index)\n```\n\n### `set_at(data, path, value, *, create=False)`\n\nSet a value in a nested data structure, optionally creating intermediate containers as needed.\n\n**Parameters:**\n\n- `data`: The data structure to modify (must be mutable: dict or list)\n- `path`: Path where to set the value (string with dot notation or list of keys/indices)\n- `value`: The value to set\n- `create`: If `True`, automatically creates missing intermediate containers (default: `False`)\n\n**Note:** \n- By default (`create=False`), `set_at` raises `PathError` if any intermediate key is missing\n- With `create=True`, missing containers are automatically created: `{}` for dict keys, `[]` for list indices\n- Positive indices can append to lists (index == len(list)) but cannot create gaps (index \u003e len(list))\n- Negative indices can only modify existing elements\n\n**Examples:**\n\n```python\n# create=True - auto-create missing containers\ndata = {}\nset_at(data, \"user.profile.name\", \"Alice\", create=True)\n# Creates: {\"user\": {\"profile\": {\"name\": \"Alice\"}}}\n\ndata = {}\nset_at(data, \"items.0.name\", \"Item 1\", create=True)\n# Creates: {\"items\": [{\"name\": \"Item 1\"}]}\n\n# Sequential list appending (no gaps allowed)\ndata = {}\nset_at(data, \"items.0\", \"first\", create=True)  # Creates list with first item\nset_at(data, \"items.1\", \"second\", create=True)  # Appends second item\n# Creates: {\"items\": [\"first\", \"second\"]}\n\n# Sparse lists are NOT allowed - this raises PathError\ndata = [1, 2, 3]\nset_at(data, \"5\", 99, create=True)  # Raises PathError: cannot create gap\n\n# Negative indices - modify existing only\ndata = [1, 2, 3]\nset_at(data, \"-1\", 100)  # Updates existing last element\n# Creates: [1, 2, 100]\n```\n\n### `exists_at(data, path)`\n\nCheck if a path exists in a nested data structure.\n\n**Parameters:**\n\n- `data`: The data structure to navigate (dict, list, tuple, or nested combinations)\n- `path`: Path to check (string with dot notation or list of keys/indices)\n\n**Returns:** `True` if the path exists, `False` otherwise\n\n**Raises:** `PathError` if the path format is invalid\n\n**Examples:**\n\n```python\ndata = {\"a\": {\"b\": {\"c\": 5}}}\nexists_at(data, \"a.b.c\")  # True\nexists_at(data, \"a.b.d\")  # False\n\ndata = {\"items\": [{\"name\": \"apple\"}, {\"name\": \"banana\"}]}\nexists_at(data, \"items.1.name\")  # True\nexists_at(data, \"items.5.name\")  # False\nexists_at(data, \"items.-1.name\")  # True (negative index)\n```\n\n### `delete_at(data, path, allow_list_mutation=False)`\n\nDelete a value from a nested data structure.\n\n**Parameters:**\n\n- `data`: The data structure to modify\n- `path`: Path to the value to delete\n- `allow_list_mutation`: If `True`, allows deletion from lists (default: `False`)\n\n**Note:** List deletion is disabled by default to prevent accidental index shifting that could break subsequent code. When you delete an element from a list, all following indices shift down, which can cause unexpected behavior if other parts of your code reference those indices.\n\n**Returns:** The deleted value\n\n**Raises:** `PathError` if the path doesn't exist or deletion is not allowed\n\n**Examples:**\n\n```python\ndata = {\"a\": {\"b\": 1, \"c\": 2}}\ndelete_at(data, \"a.b\")  # Returns 1, data becomes {\"a\": {\"c\": 2}}\n\ndata = {\"items\": [1, 2, 3]}\ndelete_at(data, \"items.1\", allow_list_mutation=True)  # Returns 2\n# data becomes {\"items\": [1, 3]}\n```\n\n### `get_depth(data)`\n\nGet the maximum nesting depth of a data structure.\n\n**Parameters:**\n\n- `data`: Any nested structure (dict, list, tuple, or primitive)\n\n**Returns:** Integer depth. Primitives return 0, empty containers return 1.\n\n**Note:** Only dict, list, and tuple are traversed. Other container types (set, frozenset, etc.) are treated as leaf values.\n\n**Examples:**\n\n```python\nget_depth(42)                          # 0 (primitive)\nget_depth({})                          # 1 (empty container)\nget_depth({\"a\": 1})                    # 1 (flat dict)\nget_depth({\"a\": {\"b\": 1}})             # 2 (nested)\nget_depth({\"a\": {\"b\": {\"c\": 1}}})      # 3 (deeper nesting)\nget_depth([1, [2, [3]]])               # 3 (nested lists)\n```\n\n### `count_leaves(data)`\n\nCount the total number of leaf values (non-container values) in a nested structure.\n\n**Parameters:**\n\n- `data`: Any nested structure\n\n**Returns:** Integer count of leaf values. Empty containers return 0.\n\n**Note:** Only dict, list, and tuple are traversed. Other container types (set, frozenset, etc.) count as a single leaf.\n\n**Examples:**\n\n```python\ncount_leaves(42)                       # 1 (primitive is a leaf)\ncount_leaves({})                       # 0 (empty container)\ncount_leaves({\"a\": 1, \"b\": 2})         # 2 (two leaf values)\ncount_leaves({\"a\": {\"b\": 1, \"c\": 2}})  # 2 (nested, still 2 leaves)\ncount_leaves([1, 2, [3, 4]])           # 4 (four leaf values)\n```\n\n### `get_all_paths(data)`\n\nGet all paths to leaf values in a nested structure.\n\n**Parameters:**\n\n- `data`: Any nested structure\n\n**Returns:** List of paths, where each path is a list of keys (strings) and indices (integers).\n\n**Note:** Only dict, list, and tuple are traversed. Other container types are treated as leaves.\n\n**Examples:**\n\n```python\nget_all_paths({\"a\": 1, \"b\": 2})\n# [[\"a\"], [\"b\"]]\n\nget_all_paths({\"a\": {\"b\": 1, \"c\": 2}})\n# [[\"a\", \"b\"], [\"a\", \"c\"]]\n\nget_all_paths({\"users\": [{\"name\": \"Alice\"}, {\"name\": \"Bob\"}]})\n# [[\"users\", 0, \"name\"], [\"users\", 1, \"name\"]]\n\nget_all_paths({})                      # [] (no leaves)\nget_all_paths(42)                      # [[]] (primitive has empty path)\n```\n\n## Error Handling\n\nThe library uses `PathError` exceptions with error codes for different failure scenarios:\n\n```python\nfrom nestedutils import PathError, PathErrorCode\n\ntry:\n    set_at(data, \"invalid.path\", 1)\nexcept PathError as e:\n    print(e.message)  # Error message\n    print(e.code)     # Error code (PathErrorCode enum)\n```\n\n**Error Codes:**\n\n| Error Code | Description |\n|------------|-------------|\n| `INVALID_PATH` | Invalid path format or type |\n| `INVALID_INDEX` | Invalid list index |\n| `MISSING_KEY` | Key doesn't exist in dictionary |\n| `EMPTY_PATH` | Path is empty |\n| `IMMUTABLE_CONTAINER` | Attempted to modify a tuple |\n| `NON_NAVIGABLE_TYPE` | Attempted to navigate into a non-container type |\n| `OPERATION_DISABLED` | Operation is disabled by configuration (e.g., list deletion without `allow_list_mutation=True`) |\n\n## Advanced Usage\n\n### Using List Paths\n\nList paths are useful when keys contain dots:\n\n```python\ndata = {}\nset_at(data, [\"user.name\", \"first\"], \"John\", create=True)\nset_at(data, [\"user.name\", \"last\"], \"Doe\", create=True)\n# Creates: {\"user.name\": {\"first\": \"John\", \"last\": \"Doe\"}}\n```\n\n### Negative List Indices\n\nNegative indices work like Python list indexing for reading and updating existing elements:\n\n```python\ndata = {\"items\": [10, 20, 30]}\nget_at(data, \"items.-1\")  # 30 (last item)\nset_at(data, \"items.-1\", 999)  # Updates last item (must exist)\n```\n\n**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`.\n\n### Working with Tuples\n\nTuples are read-only. You can read from them but cannot modify:\n\n```python\ndata = {\"items\": (1, 2, 3)}\nget_at(data, \"items.0\")  # 1 (works)\nset_at(data, \"items.0\", 9)  # Raises PathError (tuples are immutable)\n```\n\n### Handling None Values\n\nThe library can navigate through `None` values when setting:\n\n```python\ndata = {\"a\": None}\nset_at(data, \"a.b.c\", 10)\n# Replaces None with container: {\"a\": {\"b\": {\"c\": 10}}}\n```\n\n## Safety Limits\n\nThe library includes built-in safety limits to prevent excessive resource usage:\n\n| Limit | Value | Description |\n|-------|-------|-------------|\n| **Maximum Path Depth** | 100 levels | Prevents deeply nested paths that could cause stack issues |\n| **Maximum List Index** | 10,000 | Prevents creating extremely large sparse lists |\n\nThese limits help protect against accidental memory exhaustion or performance issues. If you hit these limits, you'll receive a `PathError` with a clear message.\n\n\n## Migration from v1.x to v2.0\n\nVersion 2.0 introduces breaking changes to make the library safer and more predictable. If you're upgrading from v1.x, please see the [Migration Guide](https://ysskrishna.github.io/nestedutils/migration-v1-to-v2/) for detailed upgrade instructions.\n\n## Contributing\n\nContributions are welcome! Please read our [Contributing Guide](https://github.com/ysskrishna/nestedutils/blob/main/CONTRIBUTING.md) for details on our code of conduct, development setup, and the process for submitting pull requests.\n\n## Support\n\nIf you find this library helpful:\n\n- ⭐ Star the repository\n- 🐛 Report issues\n- 🔀 Submit pull requests\n- 💝 [Sponsor on GitHub](https://github.com/sponsors/ysskrishna)\n\n## License\n\nMIT © [Y. Siva Sai Krishna](https://github.com/ysskrishna) - see [LICENSE](https://github.com/ysskrishna/nestedutils/blob/main/LICENSE) file for details.\n\n\n---\n\n\u003cp align=\"left\"\u003e\n  \u003ca href=\"https://github.com/ysskrishna\"\u003eAuthor's GitHub\u003c/a\u003e •\n  \u003ca href=\"https://linkedin.com/in/ysskrishna\"\u003eAuthor's LinkedIn\u003c/a\u003e •\n  \u003ca href=\"https://github.com/ysskrishna/nestedutils/issues\"\u003eReport Issues\u003c/a\u003e •\n  \u003ca href=\"https://pypi.org/project/nestedutils/\"\u003ePackage on PyPI\u003c/a\u003e •\n  \u003ca href=\"https://ysskrishna.github.io/nestedutils/\"\u003ePackage Documentation\u003c/a\u003e •\n  \u003ca href=\"https://ysskrishna.github.io/nestedutils/playground/\"\u003ePackage Playground\u003c/a\u003e\n\u003c/p\u003e\n","funding_links":["https://github.com/sponsors/ysskrishna","https://patreon.com/ysskrishna"],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fysskrishna%2Fnestedutils","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fysskrishna%2Fnestedutils","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fysskrishna%2Fnestedutils/lists"}