Skip to main content

A Python library for easy access and manipulation of nested data structures.

Project description

Nested

A Python library for easy access and manipulation of nested data structures.

nested treats Python objects as trees: containers (dict, list, tuple, namedtuple, dataclass, attrs) form the nodes and everything else forms the leaves. It provides a concise, dot-separated field-path syntax (e.g. 'a.1.bar') for accessing, checking, updating, adding, removing, and comparing elements at arbitrary depth — without writing chains of brackets and attribute lookups by hand.

Installation

pip install gdm-nested

Quick Start

import collections
import nested

MyTuple = collections.namedtuple('MyTuple', ['foo', 'bar'])
obj = {
    'a': [1, (2, 3, 4)],
    'b': MyTuple(foo='f', bar='b'),
}

# Access deeply nested values with a dot-separated path.
nested.access_field(obj, 'a.0')      # → 1
nested.access_field(obj, 'a.1.2')    # → 4
nested.access_field(obj, 'b.bar')    # → 'b'

# Check if a path exists.
nested.check_field(obj, 'a.0')       # → True
nested.check_field(obj, 'a.99')      # → False

# Update (or upsert) a value.
nested.update_field(obj, 'b.foo', 'g')
# → {'a': [1, (2, 3, 4)], 'b': MyTuple(foo='g', bar='b')}

# Remove a field and get the popped value.
obj2, popped = nested.remove_field(obj, 'b')
# obj2   → {'a': [1, (2, 3, 4)]}
# popped → MyTuple(foo='f', bar='b')

Supported Container Types

nested recognises the following types as tree nodes:

Type Description
dict Any dict subclass, collections.OrderedDict, collections.defaultdict, ml_collections.ConfigDict
list Any list instance
tuple Plain tuples (not namedtuples)
namedtuple Any tuple-like class with a _fields attribute
dataclass Any dataclasses.dataclass instance
attrs Any attr.s / attrs.define instance

Everything else is treated as an opaque leaf.

Note: Sequences and custom containers are only recognised as structures when derived from dict or list.

Field Path Syntax

Fields are specified as dot-separated strings, tuples, or single keys:

# All of these are equivalent:
nested.access_field(obj, 'a.1.2')
nested.access_field(obj, ('a', 1, 2))
nested.access_field(obj, ('a', '1', '2'))
  • String keys address dict keys and namedtuple/dataclass/attrs field names.
  • Integer keys address list and tuple indices (including negative indices).

Comparison with dm-tree

At first glance, nested might seem similar to google-deepmind/tree, as both operate on nested Python structures (dictionaries, lists, tuples). However, they solve entirely different problems and are highly complementary.

  • tree is for Transformation: It focuses on applying operations across the entirety of a nested structure. You use tree when you want to map a function over every leaf node (tree.map_structure), flatten a complex structure into a 1D list (tree.flatten), or verify that two structures have the same exact shape.
  • nested is for Navigation and Access: It focuses on traversal and targeting. tree lacks utilities to easily grab a specific value deep inside a structure without manual indexing. nested provides the tools to safely and cleanly query, extract, and navigate to specific paths within complex, heavily-nested objects.

Rule of thumb: If you need to multiply every number in a nested dictionary by 2, use tree. If you need to safely extract a specific configuration value buried five levels deep in that dictionary, use nested.

API Reference

Accessing Fields

access_field(nested_obj, field)

Returns the value at the given field path.

nested.access_field({'x': [10, 20]}, 'x.1')  # → 20

access_fields(nested_obj, fields)

Returns a list of values for multiple field paths.

nested.access_fields({'x': 1, 'y': 2}, ['x', 'y'])  # → [1, 2]

get(nested_obj, field, value=None)

Like access_field, but returns a default value instead of raising when the field does not exist.

nested.get({'x': 1}, 'y', 'default')  # → 'default'

Checking Fields

check_field(nested_obj, field)

Returns True if the field path exists.

nested.check_field({'x': [1]}, 'x.0')  # → True
nested.check_field({'x': [1]}, 'x.5')  # → False

check_fields(nested_obj, fields)

Returns a list of booleans for multiple field paths.

all(nested.check_fields(obj, ['a', 'b']))  # True if both exist

Updating Fields

update_field(nested_obj, field, value)

Replaces the value if the field exists, or inserts it if it doesn't (upsert).

nested.update_field({'x': 1}, 'x', 2)  # → {'x': 2}
nested.update_field({'x': 1}, 'y', 2)  # → {'x': 1, 'y': 2}

update_fields(nested_obj, *field_value_tuples, **field_value_kwargs)

Batch update/upsert multiple fields at once.

nested.update_fields({'a': 1}, ('b', 2), c=3)  # → {'a': 1, 'b': 2, 'c': 3}

Replacing Fields

replace_field(nested_obj, field, value)

Like update_field, but raises KeyError if the field does not already exist.

nested.replace_field({'x': 1}, 'x', 2)  # → {'x': 2}

nested.replace_field({'x': 1}, 'y', 2)
# raises KeyError: '"y" field not in structure.'

nested.replace_field({'x': {'z': 1}}, 'x.y', 2)
# raises KeyError: '"y" field not in structure.'

replace_fields(nested_obj, *field_value_tuples, **field_value_kwargs)

Batch version of replace_field.


Adding Fields

add_field(nested_obj, field, value)

Like update_field, but raises KeyError if the field already exists.

nested.add_field({'x': 1}, 'y', 2)  # → {'x': 1, 'y': 2}

# raises KeyError: '"x" field already in structure.'
nested.add_field({'x': 1}, 'x', 2)

add_fields(nested_obj, *field_value_tuples, **field_value_kwargs)

Batch version of add_field.

Note: For sequences, elements are inserted at the given index. Order matters — additions are applied sequentially.


Removing Fields

remove_field(nested_obj, field, *, default=...)

Removes a field and returns a (modified_obj, popped_value) tuple. Pass default to suppress errors when the field is missing.

obj, val = nested.remove_field({'x': 1, 'y': 2}, 'x')  # obj → {'y': 2}, val → 1

obj, val = nested.remove_field({'x': 1, 'y': 2}, 'z', default=3)
# obj → {'y': 2}, val → 3

remove_fields(nested_obj, *fields, default=...)

Removes multiple fields sequentially, returning a (modified_obj, [popped_values]) tuple.

Note: Removals are applied sequentially, so indices may shift for list-based structures.


Introspection

field_names(nested_obj, root='')

Returns a structure parallel to nested_obj where each leaf is replaced by its dot-separated path string.

nested.field_names({'a': [1, 2], 'b': 3})  # → {'a': ['a.0', 'a.1'], 'b': 'b'}

Comparison

assert_equal(a, b, custom_is_equals=...)

Raises ValueError if two nested structures are not structurally and value-equal. NumPy arrays are compared element-wise by default. Custom comparators can be supplied via custom_is_equals.

nested.assert_equal({'x': [1, 2]}, {'x': [1, 2]})  # passes

nested.assert_equal({'x': {'y':1}}, {'x': {'y':2}})
# ValueError: The two nested structure are not equal at path: "x.y".

nested.assert_equal({'x': {'y':1}}, {'x': {'z':2}})
# ValueError: The two structures don't have the same nested structure.
# First structure: type=dict str={'x': {'y': None}}
# Second structure: type=dict str={'x': {'z': None}}

Contributing

See CONTRIBUTING.md for details.

Licence and Disclaimer

Copyright 2026 Google LLC

All software is licensed under the Apache License, Version 2.0 (Apache 2.0); you may not use this file except in compliance with the Apache 2.0 license. You may obtain a copy of the Apache 2.0 license at: https://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, all software and materials distributed here under the Apache 2.0 are distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the licenses for the specific language governing permissions and limitations under those licenses.

This is not an official Google product.

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

gdm_nested-1.0.0.tar.gz (25.0 kB view details)

Uploaded Source

Built Distribution

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

gdm_nested-1.0.0-py3-none-any.whl (21.5 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: gdm_nested-1.0.0.tar.gz
  • Upload date:
  • Size: 25.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.12

File hashes

Hashes for gdm_nested-1.0.0.tar.gz
Algorithm Hash digest
SHA256 d29c032cb8dc2c0a78b7319e92fc0df50d5ff4f5d0f4f0729171f6ffd6df93a5
MD5 3da9dc1a0d9c32313d4a6a2768206047
BLAKE2b-256 b023c0ddf03f6bd5bab2f6aca6fe216f5d4c8f4dd2a74154719b6147066dc18c

See more details on using hashes here.

File details

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

File metadata

  • Download URL: gdm_nested-1.0.0-py3-none-any.whl
  • Upload date:
  • Size: 21.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.12

File hashes

Hashes for gdm_nested-1.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 6929c4bf8321bfa5afb8405b9017056385377d4be9a37add63497c7227164894
MD5 bdfd607abd968aff91f232198240eecc
BLAKE2b-256 b9995c6f98cffae668f5d05d9f080edfdd74c238a0fd686e4ac0633105c81e9b

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