12
\$\begingroup\$

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:
        try:
            return nestedGet(d.get(p[0]),p[1:])
        except AttributeError:
            return None
    if len(p) == 1:
        try:
            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
\$\endgroup\$
8
\$\begingroup\$

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
\$\endgroup\$
6
\$\begingroup\$

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
\$\endgroup\$
4
\$\begingroup\$

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
\$\endgroup\$
2
\$\begingroup\$

I like the following approach that makes use of functools.reduce. It generalizes nicely to other indexable sequences such as lists and tuples.

from functools import reduce
def get_nested_item(data, keys):
    return reduce(lambda seq, key: seq[key], keys, data)

This can be further simplified by direct access to the __getitem__ method of the sequences, which leads to a noticeable increase in performance (I measured 20-35% for different test data):

from functools import reduce
from operator import getitem
def get_nested_item(data, keys):
    return reduce(getitem, keys, data)

Usage:

get_nested_item(data={"a":{"b":{"c":1}}}, keys=["a","b","c"]) # => 1
get_nested_item(data=[[[1,2,3],[10,20,30]]], keys=[0,1,2])    # => 30
get_nested_item(data={'a':0, 'b':[[1,2]]}, keys=['b',0,1])    # => 2
get_nested_item(data=some_sequence, keys=[])                  # => some_sequence

If one prefers soft failure for missing keys/indices, one can catch KeyError or IndexError.

def get_nested_item(data, keys):
    try:
        return reduce(getitem, keys, data)
    except (KeyError, IndexError):
        return None
\$\endgroup\$

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.