jmhobbs

Addressing Nested Dictionaries in Python

I think that post title is right. Essentially I mean dynamically accessing attributes from nested dictionaries or tuples.

Let's say you've got a JSON response like this:

{
  "error": false,
  "body": {
    "name": "John",
    "job": {
      "company": "What Cheer",
      "position": "Developer"
    }
  }
}

The JSON module would convert that into nested dictionaries, like this:

>>> print usr
{u'body': {u'job': {u'position': u'Developer', u'company': u'What Cheer'}, u'name': u'John'}, u'error': False}

In my case, I was trying to provide a runtime specified format string which could use any of the values at any depth.

There isn't (to my knowledge) an easy way to address into a deep structure with a single string. I considered value based format strings ('%(name)s' but there is no way to descend from there either.

My solution was to use a dot notation and evaluate it for field values.

This requires a strict policy not to use dots in your keys, but that is not an issue for my use case.

Here is my code for the dot notation:

def getByDotNotation( obj, ref ):
  val = obj
  for key in ref.split( '.' ):
    val = val[key]
  return val

And here it is in use against the object above:

>>> getByDotNotation( usr, 'body.name' )
u'John'
>>> getByDotNotation( usr, 'body.job.position' )
u'Developer'
>>> getByDotNotation( usr, 'error' )
False
>>> 

The next (optional) step would be to create a wrapper object.

class DotAccessibleDict ( object ):
  def __init__ ( self, data ):
    self._data = data

  def __getitem__ ( self, name ):
    val = self._data
    for key in name.split( '.' ):
      val = val[key]
    return val

Which we can then use like so:

>>> wrapped = DotAccessibleDict( usr )
>>> wrapped['body.name']
u'John'
>>> wrapped['body.job.position']
u'Developer'
>>> wrapped['error']
False
>>> wrapped['nope']
Traceback (most recent call last):
  File "", line 1, in 
  File "", line 7, in __getitem__
KeyError: 'nope'
>>> 

While this is just sugar, it does look nice doesn't it? To be complete you would want to implement the other sequence methods such as __setitem__

So that's my fix - what's yours?