3
\$\begingroup\$

So I have some data being returned to me in the form of a deeply nested Dict and I need to parse multiple values from it which may or may not be there. I saw this library (https://github.com/akesterson/dpath-python) and tried to come up with a simplified version that fits into one function.

I think its pretty good but I'm wondering if I missed something or there's some small way to improve it?

from typing import Dict, Union
import re
import copy

def dict_query(d: Dict, path: str) -> Union[object, None]:
    """
    # Example usage:
    >>> d = {'a': [{}, {'b': 9}]}
    >>> print( dict_query(d, 'a/[1]/b') )
    9
    """
    keys = path.split("/")
    val = copy.deepcopy(d)
    for key in keys:

        idx = re.search("\[(\d+)\]", key)
        if idx:
            # handle list index
            if isinstance(val, (list, tuple)):
                idx = int(idx.group(1))
                val = val[idx]
            else:
                return None
        elif isinstance(val, dict):
            # handle dict key
            val = val.get(key)
        else:
            return None
    return val

# tests of searching nested dicts
d = {'a': {'b': 1}}
print( dict_query(d, 'a/b') ) # 1
print( dict_query(d, 'a/c') ) # None
print( dict_query(d, 'a/') ) # None
print( dict_query(d, 'c/b') ) # None
# tests of searching nested dicts + lists
d = {'a': [{}, {'b': 1}]}
print( dict_query(d, 'a/[0]') ) # {}
print( dict_query(d, 'a/[1]') ) # {'b': 1}
print( dict_query(d, 'a/[1]/b') ) # 1
print( dict_query(d, 'a/[1]/c') ) # None
print( dict_query(d, 'a/[0]/a') ) # None
print( dict_query(d, '[1]/b') ) # None
\$\endgroup\$
  • 2
    \$\begingroup\$ The code doesn't change the dict, So, it's not necessary to make a deep copy of it. \$\endgroup\$ – RootTwo Oct 23 at 23:25
  • \$\begingroup\$ Please provide sample input and output \$\endgroup\$ – Aryan Parekh Oct 24 at 8:32
  • \$\begingroup\$ @AryanParekh I already did, its at the bottom of the post \$\endgroup\$ – ScrapeHeap Oct 24 at 11:04
0
\$\begingroup\$

I would skip the [] in the path. Just presume val is a dict with strings for keys (the usual case). It that doesn't work, try converting the key to an int.

def dict_query(d: Dict, path: str) -> Union[object, None]:
    """
    # Example usage:
    >>> d = {'a': [{}, {'b': 9}]}
    >>> print( dict_query(d, 'a/1/b') )
    9
    """
    keys = path.split("/")
    val = d

    try:
        for key in keys:
            try:
                val = val[key]
                
            except (KeyError, TypeError):
                val = val[int(key)]
        
        return val
    
    except (IndexError, KeyError, TypeError, ValueError):
        return None
| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ except (IndexError, KeyError, TypeError): return None this seems a bit risky to me \$\endgroup\$ – ScrapeHeap Oct 24 at 2:13
  • \$\begingroup\$ The outer try-except block really only covers one line: val = val[int(key)]. The errors correspond to val being a list and key being out of range; val being a dict and missing the key; val not being a Mapping or Sequence. Ooops, I forgot the ValueError for key not being an int. \$\endgroup\$ – RootTwo Oct 24 at 6:17

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.