Python Dictionary as Object

Python Dictionary as Object

Update 16Aug2022: When I shared this on LinkedIn, Ian Broad commented to let me know about a great Python project doing exactly this, Bunch. As is frequently the case with Python, the solution is a pip or conda install away!

It's something simple enough, I just want to take a dictionary and have the ablity to access the properties using dot syntax.

Consider, if I have a dictionary such as the following...

my_dict = {
    'one_value': 'something sweet',
    'two_value': 'something even sweeter',
    'nested_value': {
        'n1': 'buried value',
        'n2': 'another buried value'
    },
    'nested_list': [
        {
            'first_nested': 'really getting confusing',
        },
        {
            'second_nested': 'are we following all of this?"
        }
    ]
}

Normally, to access the values in the dictionary, I need to make sure and very specifically reference all the right values with no typos. If I want to retrieve the value saved in the n1 key, I need to correctly type...

my_dict['nested_value']['n1']

Since I work in Jupyter most of the time, I take advantage of completion and introspection to discover properties and avoid typos. Python dictionaries do not help much with this, but Python Objects do. This logically begs the question, how to create the nested object heirarchy so I can access the same value using dot syntax.

my_dict.nested_value.n1

While with simple dictionaries this is not a huge issue, when working with responses from REST endpoints, the returned JSON messages can get absolutely huge. Easier access to all the properties makes life a lot easier. The following small object declaration can be used to convert deeply nested dictionaries into nested objects.

class DictObj:
    def __init__(self, in_dict:dict):
    	assert isinstance(in_dict, dict)
        for key, val in in_dict.items():
            if isinstance(val, (list, tuple)):
               setattr(self, key, [DictObj(x) if isinstance(x, dict) else x for x in val])
            else:
               setattr(self, key, DictObj(val) if isinstance(val, dict) else val)

This makes digging into deep JSON responses from REST endpoints a lot easier. Starting simply enough, the above dictionary is converted into a nested object.

my_obj = DictObt(my_dict)

Then, accessing properties is as simple as using dot syntax.

my_obj.nested_value.n1

Since this is something I frequently encoounter when digging into JSON from REST responses, I will use the USGS Real-Time Streamflows as a real world example.

import requests

url = 'https://waterservices.usgs.gov/nwis/iv/'
params = {
    'format': 'json',
    'sites': '12080010',
    'parameterCd': '00060',
    'siteStatus': 'all'
}

# retrieve the most recent real time streamflow observations
res = requests.get(url, params=params)

# convert the json body into a dictionary
res_json = res.json()

# convert the json to a nested object
res_obj = DictObj(res_json)

# dig deep into the response to get the most recent (last in list) value
str_obs = res_obj.value.timeSeries[-1].values[-1].value[-1].value

# since stuck as a string, convert to an integer, and done!
int_obs = int(str_obs)

This little trick has made my life a lot easier when dealing with tangled JSON coming back from REST endpoints. Hopefully, it also makes your life a little easier as well!