'python check multi-level dict key existence

Many SO posts show you how to efficiently check the existence of a key in a dictionary, e.g., Check if a given key already exists in a dictionary

How do I do this for a multi level key? For example, if d["a"]["b"] is a dict, how can I check if d["a"]["b"]["c"]["d"] exists without doing something horrendous like this:

if "a" in d and isInstance(d["a"], dict) and "b" in d["a"] and isInstance(d["a"]["b"], dict) and ...

Is there some syntax like

if "a"/"b"/"c"/"d" in d

What I am actually using this for: we have jsons, parsed into dicts using simplejson, that I need to extract values from. Some of these values are nested three and four levels deep; but sometimes the value doesn't exist at all. So I wanted something like:

val = None if not d["a"]["b"]["c"]["d"] else  d["a"]["b"]["c"]["d"] #here d["a"]["b"] may not even exist

EDIT: prefer not to crash if some subkey exists but is not a dictionary, e.g, d["a"]["b"] = 5.



Solution 1:[1]

UPDATE: I ended up writing my own open-source, pippable library that allows one to do this: https://pypi.python.org/pypi/dictsearch

Solution 2:[2]

Sadly, there isn't any builtin syntax or a common library to query dictionaries like that.

However, I believe the simplest(and I think it's efficient enough) thing you can do is:

d.get("a", {}).get("b", {}).get("c")

Edit: It's not very common, but there is: https://github.com/akesterson/dpath-python

Edit 2: Examples:

>>> d = {"a": {"b": {}}}
>>> d.get("a", {}).get("b", {}).get("c")
>>> d = {"a": {}}
>>> d.get("a", {}).get("b", {}).get("c")
>>> d = {"a": {"b": {"c": 4}}}
>>> d.get("a", {}).get("b", {}).get("c")
4

Solution 3:[3]

This isn't probably a good idea and I wouldn't recommend using this in prod. However, if you're just doing it for learning purposes then the below might work for you.

def rget(dct, keys, default=None):
    """
    >>> rget({'a': 1}, ['a'])
    1
    >>> rget({'a': {'b': 2}}, ['a', 'b'])
    2
    """
    key = keys.pop(0)
    try:
        elem = dct[key]
    except KeyError:
        return default
    except TypeError:
        # you gotta handle non dict types here
        # beware of sequences when your keys are integers
    if not keys:
        return elem
    return rget(elem, keys, default)

Solution 4:[4]

A non-recursive version, quite similar to @Meitham's solution, which does not mutate the looked-for key. Returns True/False if the exact structure is present in the source dictionary.

def subkey_in_dict(dct, subkey):
    """ Returns True if the given subkey is present within the structure of the source dictionary, False otherwise.
    The format of the subkey is parent_key:sub_key1:sub_sub_key2 (etc.) - description of the dict structure, where the
    character ":" is the delemiter.

    :param dct: the dictionary to be searched in.
    :param subkey: the target keys structure, which should be present.
    :returns Boolean: is the keys structure present in dct.
    :raises AttributeError: if subkey is not a string.
    """
    keys = subkey.split(':')
    work_dict = dct
    while keys:
        target = keys.pop(0)
        if isinstance(work_dict, dict):
            if target in work_dict:
                if not keys:    # this is the last element in the input, and it is in the dict
                    return True
                else:   # not the last element of subkey, change the temp var
                    work_dict = work_dict[target]
            else:
                return False
        else:
            return False

The structure that is checked is in the form parent_key:sub_key1:sub_sub_key2, where the : char is the delimiter. Obviously - it will match case-sensitively, and will stop (return False) if there's a list within the dictionary.

Sample usage:

dct = {'a': {'b': {'c': {'d': 123}}}}

print(subkey_in_dict(dct, 'a:b:c:d'))    # prints True
print(subkey_in_dict(dct, 'a:b:c:d:e'))  # False
print(subkey_in_dict(dct, 'a:b:d'))      # False
print(subkey_in_dict(dct, 'a:b:c'))      # True

Solution 5:[5]

This is what I usually use

def key_in_dict(_dict: dict, key_lookup: str, separator='.'):
    """
        Searches for a nested key in a dictionary and returns its value, or None if nothing was found.
        key_lookup must be a string where each key is deparated by a given "separator" character, which by default is a dot
    """
    keys = key_lookup.split(separator)
    subdict = _dict

    for k in keys:
        subdict = subdict[k] if k in subdict else None
        if subdict is None: break

    return subdict

Returns the key if exists, or None it it doesn't

key_in_dict({'test': {'test': 'found'}}, 'test.test') // 'found'
key_in_dict({'test': {'test': 'found'}}, 'test.not_a_key') // None

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2
Solution 3 Meitham
Solution 4 Todor Minakov
Solution 5