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?
CodePudding user response:
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)
CodePudding user response:
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)
CodePudding user response:
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 adict, 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 aTypeErrorfor anything that is not iterable; the next line will still raise thatTypeErrorfor 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__ofdict; callingself[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
