Reputation: 3120
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
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
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