1

I have a variable length nested dictionary being read from an api response and I want to be able to read the value of a key that could be deeply nested.

For example:

responses = {'animal': {'name': 'lassy'}}

print(responses['animal']['name'])

However I do not know the length of the dictonary in the response, I could be asked to find the value of "foo.bar.foo.bar"

Is there a way to set up my code that no matter the length of the response I can access the needed value?

4 Answers4

0

Loop through a list of keys, replacing a reference to the current level with the value from the next key.

responses = {'animal': {'name': 'lassy'}}
keys = ['animal', 'name']

r = responses
for key in keys:
    r = r[key]

print(r)
Barmar
  • 741,623
  • 53
  • 500
  • 612
0

I guess you could just split the response <string>.split(".") then using a loop you can go throught

query = "animal.name"
responses = {'animal': {'name': 'lassy'}}

query_array = query.split('.')
result = response;
for key in query_array:
  if key not in result:
    print('error')
    break
  result = result[key]
print(result)
SCcagg5
  • 576
  • 3
  • 15
0

A nice way might be to write a class that does this generically:

from typing import Hashable


class CompoundKeyDict(dict):
    def __getitem__(self, item):
        try:
            __ = iter(item)
            if isinstance(item, Hashable):
                 raise TypeError() 
            value = None
            for key in item:
                value = super().__getitem__(key) if value is None else value[key]
            return value
        except TypeError:
            return super().__getitem__(item)


responses = CompoundKeyDict({'animal': {'name': 'lassy'}})

# this still works
print(responses['animal'])
# but this now also works 
print(responses[['animal', 'name']])
# so you can do something like this as well
print(responses['animal.name'.split('.')])

print(isinstance(responses, dict))

With that class available, you can take just any dict (say d) and access it with a list, generator, or any other iterable by casting it to a CompoundKeyDict with d = CompoundKeyDict(d).

A few remarks on how/why it works:

  • __getitem__ gets overridden to change the standard behaviour of accessing a dict, but since non-hashable iterables aren't a valid key (because they are mutable), this only affects the cases you need.
  • __ = iter(item) this line will fail with a TypeError for anything that is not iterable; the next line will still raise that TypeError for anything that is iterable, but also hashable (like a string, or a tuple); so you're left only with keys that are iterable, but not hashable, like a list or a generator.
  • Note that super().__getitem__(key) is required, this calls the original __getitem__ of dict; calling self[key] would cause an infinite loop
  • Note that you could still have iterables like a tuple as a key, for example print(responses[('animal', 'name')]) would just throw a KeyError, unless there's an entry that has ('animal', 'name') as its key.

Result:

{'name': 'lassy'}
lassy
lassy
True
Grismar
  • 27,561
  • 4
  • 31
  • 54
0

you need recursion:

def myprint(d):
    for k, v in d.items():
        if isinstance(v, dict):
            myprint(v)
        else:
            print("{0} : {1}".format(k, v))

You can find the complete answer here: Loop through all nested dictionary values?

araz malek
  • 21
  • 4