Ryan Schaefer
Ryan Schaefer

Reputation: 3120

Creating a namespace with a dict of dicts

Currently I am using json to save a dict to a config file. I load this to turn it into a dict then turn it into a SimpleNamespace because I prefer dot notation to access the settings. To do this, I load it as in this example:

import json
from types import SimpleNamespace
SETTINGS = json.load(open("config.json", 'r'))
SETTINGS = SimpleNamespace(**SETTINGS)

However, as I am currently loading the dict into a SimpleNamespace it is not loading the sub dicts within the config file. So for example if I do:

SETTINGS.server_info.port

I get the error:

AttributeError: 'dict' object has no attribute 'port'

I was wondering how I load all dicts into the namespace as name spaces so that I am able to use dot notation all the way down the dictionary.

Upvotes: 9

Views: 5960

Answers (2)

larsks
larsks

Reputation: 311750

It is not necessary to recursively apply the transformation to the data returned by json.load. You can simply ask json.load to return SimpleNamespace instances instead of dictionaries by providing an object_hook method to the json.load call. This method "will be called with the result of every JSON object decoded and its return value will be used in place of the given dict." (from the docs).

The simplest object_hook might look like:

def dict_to_sns(d):
    return SimpleNamespace(**d)

For example, given the following input:

{
  "settings": {
    "foo": {
      "number": 4,
      "size": "large"
    },
    "bar": {
      "color": "orange",
      "widgets": [
        "gizmo",
        "gadget",
        "thing"
      ]
    }
  }
}

We can do the following:

>>> import json
>>> from types import SimpleNamespace
>>> def dict_to_sns(d):
...     return SimpleNamespace(**d)
... 
>>> with open('settings.json') as fd:
...     data = json.load(fd, object_hook=dict_to_sns)
... 
>>> data
namespace(settings=namespace(bar=namespace(color='orange', widgets=['gizmo', 'gadget', 'thing']), foo=namespace(number=4, size='large')))
>>> data.settings.foo
namespace(number=4, size='large')
>>> data.settings.foo.number
4
>>> data.settings.bar.widgets
['gizmo', 'gadget', 'thing']

Upvotes: 23

Martijn Pieters
Martijn Pieters

Reputation: 1122512

You'll have to recursively apply the SimpleNamespace class to nested dictionaries; I prefer to use @functools.singledispatch() for such cases:

from functools import singledispatch
from types import SimpleNamespace

@singledispatch
def wrap_namespace(ob):
    return ob

@wrap_namespace.register(dict)
def _wrap_dict(ob):
    return SimpleNamespace(**{k: wrap_namespace(v) for k, v in ob.items()})

@wrap_namespace.register(list)
def _wrap_list(ob):
    return [wrap_namespace(v) for v in ob]

then use this as:

with open('config.json') as settings_file:
    SETTINGS = wrap_namespace(json.load(settings_file))

Demo:

>>> SETTINGS = wrap_namespace({'foo': 'bar', 'ham': {'spam': 'eggs', 'monty': [{'name': 'Eric Idle'}]}})
>>> SETTINGS.foo
'bar'
>>> SETTINGS.ham.monty[0].name
'Eric Idle'

Upvotes: 13

Related Questions