Nested dictionary value from key path
Solution 1:
This is an instance of a fold. You can either write it concisely like this:
from functools import reduce
import operator
def find(element, json):
return reduce(operator.getitem, element.split('.'), json)
Or more Pythonically (because reduce()
is frowned upon due to poor readability) like this:
def find(element, json):
keys = element.split('.')
rv = json
for key in keys:
rv = rv[key]
return rv
j = {"app": {
"Garden": {
"Flowers": {
"Red flower": "Rose",
"White Flower": "Jasmine",
"Yellow Flower": "Marigold"
}
},
"Fruits": {
"Yellow fruit": "Mango",
"Green fruit": "Guava",
"White Flower": "groovy"
},
"Trees": {
"label": {
"Yellow fruit": "Pumpkin",
"White Flower": "Bogan"
}
}
}}
print find('app.Garden.Flowers.White Flower', j)
Solution 2:
I was in a similar situation and found this dpath module. Nice and easy.
Solution 3:
Your code heavily depends on no dots every occurring in the key names, which you might be able to control, but not necessarily.
I would go for a generic solution using a list of element names and then generate the list e.g. by splitting a dotted list of key names:
class ExtendedDict(dict):
"""changes a normal dict into one where you can hand a list
as first argument to .get() and it will do a recursive lookup
result = x.get(['a', 'b', 'c'], default_val)
"""
def multi_level_get(self, key, default=None):
if not isinstance(key, list):
return self.get(key, default)
# assume that the key is a list of recursively accessible dicts
def get_one_level(key_list, level, d):
if level >= len(key_list):
if level > len(key_list):
raise IndexError
return d[key_list[level-1]]
return get_one_level(key_list, level+1, d[key_list[level-1]])
try:
return get_one_level(key, 1, self)
except KeyError:
return default
get = multi_level_get # if you delete this, you can still use the multi_level-get
Once you have this class it is easy to just transform your dict and get "Jasmine":
json = {
"app": {
"Garden": {
"Flowers": {
"Red flower": "Rose",
"White Flower": "Jasmine",
"Yellow Flower": "Marigold"
}
},
"Fruits": {
"Yellow fruit": "Mango",
"Green fruit": "Guava",
"White Flower": "groovy"
},
"Trees": {
"label": {
"Yellow fruit": "Pumpkin",
"White Flower": "Bogan"
}
}
}
}
j = ExtendedDict(json)
print j.get('app.Garden.Flowers.White Flower'.split('.'))
will get you:
Jasmine
Like with a normal get()
from a dict, you get None
if the key (list) you specified doesn't exists anywhere in the tree, and you can specify a second parameter as return value instead of None
Solution 4:
I suggest you to use python-benedict
, a python dict subclass with full keypath support and many utility methods.
You just need to cast your existing dict:
d = benedict(json)
# now your keys support dotted keypaths
print(d['app.Garden.Flower.White Flower'])
Here the library and the documentation: https://github.com/fabiocaccamo/python-benedict
Note: I am the author of this project
Solution 5:
Very close. You need to (as you had in your comment) recursively go through the main JSON object. You can accomplish that by storing the result of the outermost key/value, then using that to get the next key/value, etc. till you're out of paths.
def find(element, JSON):
paths = element.split(".")
data = JSON
for i in range(0,len(paths)):
data = data[paths[i]]
print data
You still need to watch out for KeyErrors though.