4
\$\begingroup\$

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 the dpath library and tried to come up with a simplified version that fits into one function.

I think it's 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\$
3
  • 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, 2020 at 23:25
  • \$\begingroup\$ Please provide sample input and output \$\endgroup\$
    – user228914
    Oct 24, 2020 at 8:32
  • \$\begingroup\$ @AryanParekh I already did, its at the bottom of the post \$\endgroup\$
    – ScrapeHeap
    Oct 24, 2020 at 11:04

1 Answer 1

1
\$\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
\$\endgroup\$
2
  • \$\begingroup\$ except (IndexError, KeyError, TypeError): return None this seems a bit risky to me \$\endgroup\$
    – ScrapeHeap
    Oct 24, 2020 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, 2020 at 6:17

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

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