I would like to get a deeply-nested value, for example {"a":{"b":{"c":"myValue"}} by providing the keys to traverse. I tried chaining together .get() but that didn't work for cases where some of the keys were missing—in those cases, I want to get None instead.

Could this solution be improved?

#!/usr/bin/env python
# encoding: utf-8

def nestedGet(d,p):
    if len(p) > 1:
            return nestedGet(d.get(p[0]),p[1:])
        except AttributeError:
            return None
    if len(p) == 1:
            return d.get(p[0])
        except AttributeError:
            return None

print nestedGet({"a":{"b":{"c":1}}},["a","b","c"]) #1
print nestedGet({"a":{"bar":{"c":1}}},["a","b","c"]) #None

Unless I'm missing something you'd like to implement, I would go for a simple loop instead of using recursion:

def nested_get(input_dict, nested_key):
    internal_dict_value = input_dict
    for k in nested_key:
        internal_dict_value = internal_dict_value.get(k, None)
        if internal_dict_value is None:
            return None
    return internal_dict_value

print(nested_get({"a":{"b":{"c":1}}},["a","b","c"])) #1
print(nested_get({"a":{"bar":{"c":1}}},["a","b","c"])) #None

If you were to solve it with a third-party package, like jsonpath-rw, the solution would be as simple as constructing the path by joining the keys with a dot and parsing the dictionary:

from jsonpath_rw import parse

def nested_get(d, path):
    result = parse(".".join(path)).find(d)
    return result[0].value if result else None

Some code style notes about your current approach:

  • Python functions does not follow the camel case naming conventions, put an underscore between words - nested_get() instead of nestedGet()
  • there should be extra spaces after the commas (PEP8 reference)
  • improve on variable naming, make them more descriptive - for instance, p can be named as path
  • use print() function instead of a statement for Python3.x compatibility

You can chain together dict.get() functions and use the optional default argument and specify an empty dict to return instead of None if the key doesn't exist. Except, let the last .get() return None if the key is not found.

If any of the keys to be traversed are missing, a empty dict is returned and the following .get()s in the chain will be called on the empty dict, which is fine., The final .get() in the chain will either return the value if all the keys exist or will return None as desired.

example 1:

input_dict = {"a":{"b":{"c":1}}} #1
input_dict.get("a", {}).get("b", {}).get("c") # 1

example 2:

input_dict = {"a":{"bar":{"c":1}}} #1
input_dict.get("a", {}).get("b", {}).get("c") # None

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.