Skip to main content

Nested OBject manipulations

Project description

nob: the Nested OBject manipulator

JSON is a very popular format for nested data exchange, and Object Relational Mapping (ORM) is a popular method to help developers make sense of large JSON objects, by mapping objects to the data. In some cases however, the nesting can be very deep, and difficult to map with objects. This is where nob can be useful: it offers a simple set of tools to explore and edit any nested data (Python native dicts and lists).

For more, checkout the home page, or play around with the library in colab:

[Open In Colab](]



nob.Tree objects can be instantiated directly from a Python dictionary:

t = Tree({
    'key1': 'val1',
    'key2': {
        'key3': 4,
        'key4': {'key5': 'val2'},
        'key5': [3, 4, 5]
    'key5': 'val3'

To create a Tree from a JSON (or YAML) file, simply read it and feed the data to the constructor:

import json
with open('file.json') as fh:
    t2 = Tree(json.load(fh))

import yaml
with open('file.yml') as fh:
    t3 = Tree(yaml.load(fh))

Similarly, to create a JSON (YAML) file from a tree, you can use:

with open('file.json', 'w') as fh:
    json.dump(t2.val, fh)

with open('file.yml', 'w') as fh:
    yaml.dump(t3.val, fh)

Basic manipulation

The variable t now holds a tree, i.e the reference to the actual data. However, for many practical cases it is useful to work with a subtree. nob offers a useful class TreeView to this end. It handles identically for the most part as the main tree, but changes performed on a TreeView affect the main Tree instance that it is linked to. In practice, any access to a key of t yields a TreeView instance, e.g.:

tv1 = t['/key1']         # TreeView(/key1)
tv2 = t['key1']          # TreeView(/key1)
tv3 = t.key1             # TreeView(/key1)
tv1 == tv2 == tv3        # True

Note that a full path '/key1', as well as a simple key 'key1' are valid identifiers. Simple keys can also be called as attributes, using t.key1.

To access the actual value that is stored in the nested object, simply use the .val method:

tv1.val                  >>> 'val1'
t.key1.val               >>> 'val1'

To assign a new value to this node, you can do it directly on the TreeView instance:

t.key1 = 'new'
tv1.val                  >>> 'new'
t.val['key1']            >>> 'new'

Of course, because of how Python variables work, you cannot simply assign the value to tv1, as this would just overwrite it's contents:

tv1 = 'new'
tv1                      >>> 'new'
t.val['key1']            >>> 'val1'

If you find yourself with a TreeView object that you would like to edit directly, you can use the .set method:

tv1 = t.key1
t.val['key1']            >>> 'new'

Because nested objects can contain both dicts and lists, integers are sometimes needed as keys:

t['/key2/key5/0']        >>> TreeView(/key2/key5/0)
t.key2.key5[0]           >>> TreeView(/key2/key5/0)
t.key2.key5['0']         >>> TreeView(/key2/key5/0)

However, since Python does not support attributes starting with an integer, there is no attribute support for lists. Only key access (full path, integer index or its stringified counterpart) are supported.

Smart key access

In a simple nested dictionary, the access to 'key1' would be simply done with:


If you are looking for e.g. key3, you would need to write:


For deep nested objects however, this can be a chore, and become very difficult to read. nob helps you here by supplying a smart method for finding unique keys:

t['key3']                >>> TreeView(/key2/key3)
t.key3                   >>> TreeView(/key2/key3)

Note that attribute access t.key3 behaves like simple key access t['key3']. This has some implications when the key is not unique in the tree. Let's say e.g. we wish to access key5. Let's try using attribute access:

t.key5                   >>> KeyError: Identifier key5 yielded 3 results instead of 1

Oups! Because key5 is not unique (it appears 3 times in the tree), t.key5 is not specific, and nob wouldn't know which one to return. In this instance, we have several possibilities, depending on which key5 we are looking for:

t.key4.key5              >>> TreeView(/key2/key4/key5)
t.key2['/key5']          >>> TreeView(/key2/key5)
t['/key5']               >>> TreeView(/key5)

There is a bit to unpack here:

  • The first key5 is unique in the TreeView t.key4 (and key4 is itself unique), so t.key4.key5 finds it correctly.
  • The second is complex: key2 is unique, but key5 is still not unique to t.key2. There is not much advantage compared to a full path access t['/key2/key5'].
  • The last cannot be resolved using keys in its path, because there are none. The only solution is to use a full path.

Other tree tools

Paths: any Tree (or TreeView) object can introspect itself to find all its valid paths:

t.paths                  >>> [Path('/'),

Find: in order to easily search in this path list, the .find method is available:

t.find('key5')           >>> [Path('/key2/key4/key5'),

The elements of these lists are not strings, but Path objects, as described below.

Iterable: any tree or tree view is also iterable, yielding its children:

[tv for tv in t.key2]    >>> [TreeView(/key2/key3),

Copy: to make an independant copy of a tree, use its .copy() method:

t_cop = t.copy()
t == t_cop               >>> True
t_cop.key1 = 'new_val'
t == t_cop               >>> False

A new standalone tree can also be produced from any tree view:

t_cop = t.key2.copy()
t_cop == t.key2          >>> True
t_cop.key3 = 5
t_cop == t.key2          >>> False


All paths are stored internally using the nob.Path class. Paths are full (w.r.t. their Tree or TreeView), and are in essence a list of the keys constituting the nested address. They can however be viewed equivalently as a unix-type path string with / separators. Here are some examples

p1 = Path(['key1'])
p1                       >>> Path(/key1)
p2 = Path('/key1/key2')
p2                       >>> Path(/key1/key2)
p1 / 'key3'              >>> Path(/key1/key3)
p2.parent                >>> Path(/key1)
p2.parent == p1          >>> True
'key2' in p2             >>> True
[k for k in p2]          >>> ['key1', 'key2']
p2[-1]                   >>> 'key2'
len(p2)                  >>> 2

These can be helpful to manipulate paths yourself, as any full access with a string to a Tree or TreeView object also accepts a Path object. So say you are accessing the keys in list_of_keys at one position, but that thet also exist elsewhere in the tree. You could use e.g.:

root = Path('/path/to/root/of/keys')
[t[root / key] for key in list_of_keys]

Project details

Download files

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

Source Distributions

No source distribution files available for this release.See tutorial on generating distribution archives.

Built Distribution

nob-0.3.1-py2.py3-none-any.whl (9.0 kB view hashes)

Uploaded Python 2 Python 3

Supported by

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