1

I am parsing unknown nested json object, I do not know the structure nor depth ahead of time. I am trying to search through it to find a value. This is what I came up with, but I find it fugly. Could anybody let me know how to make this look more pythonic and cleaner?

def find(d, key):
    if isinstance(d, dict):
        for k, v in d.iteritems():
            try:
                if key in str(v):
                    return 'found'
            except:
                continue
            if isinstance(v, dict):
                for key,value in v.iteritems():
                    try:
                        if key in str(value):
                            return "found"
                    except:
                        continue
                    if isinstance(v, dict):
                        find(v)
                    elif isinstance(v, list):
                        for x in v:
                            find(x)
    if isinstance(d, list):
        for x in d:
            try:
                if key in x:
                    return "found"
            except:
                continue
            if isinstance(v, dict):
                find(v)
            elif isinstance(v, list):
                for x in v:
                    find(x)
    else:
        if key in str(d):
            return "found"
        else:
            return "Not Found"
Jacek Perry
  • 393
  • 6
  • 16
  • 6
    If it works, check out https://codereview.stackexchange.com/ – cs95 May 07 '18 at 02:51
  • Never heard of it, will have to go there. Yeah it works well, but I find it ugly as all heck. Thanks! – Jacek Perry May 07 '18 at 03:27
  • 1
    By the way, can't you just use something like `import re; re.search(r'\b{}\b'.format(key), str(d))`? – cs95 May 07 '18 at 03:28
  • Related: [Find all occurrences of a key in nested python dictionaries and lists](https://stackoverflow.com/questions/9807634/find-all-occurrences-of-a-key-in-nested-python-dictionaries-and-lists). Possible duplicate? – smci May 07 '18 at 05:28
  • 1
    No, this isn't about key but rather value. Searching for key is far easier, at least in my experience as all you have to do is ensure that you are looking within dictionary. – Jacek Perry May 07 '18 at 21:05
  • Jacek: but your question statement says otherwise, please fix it. You're not trying to find the value for a given key, only return "found"/"not found" if that key occurs. Code like `if key in str(value)` seriously should be avoided, please don't interchange the two; for recursing into a nested structure, you could say 'element' or 'subelem'. – smci May 07 '18 at 23:45
  • You could refer to the Python style guide https://www.python.org/dev/peps/pep-0008/ . For example, variable names are often written out in Python rather than single letter abbreviations. Make it read like English when possible. – Bennett Brown May 08 '18 at 01:25

1 Answers1

2

It is generally more "Pythonic" to use duck typing; i.e., just try to search for your target rather than using isinstance. See What are the differences between type() and isinstance()?

However, your need for recursion makes it necessary to recurse the values of the dictionaries and the elements of the list. (Do you also want to search the keys of the dictionaries?)

The in operator can be used for both strings, lists, and dictionaries, so no need to separate the dictionaries from the lists when testing for membership. Assuming you don't want to test for the target as a substring, do use isinstance(basestring) per the previous link. To test whether your target is among the values of a dictionary, test for membership in your_dictionary.values(). See Get key by value in dictionary

Because the dictionary values might be lists or dictionaries, I still might test for dictionary and list types the way you did, but I mention that you can cover both list elements and dictionary keys with a single statement because you ask about being Pythonic, and using an overloaded oeprator like in across two types is typical of Python.

Your idea to use recursion is necessary, but I wouldn't define the function with the name find because that is a Python built-in which you will (sort of) shadow and make the recursive call less readable because another programmer might mistakenly think you're calling the built-in (and as good practice, you might want to leave the usual access to the built in in case you want to call it.)

To test for numeric types, use `numbers.Number' as described at How can I check if my python object is a number?

Also, there is a solution to a variation of your problem at https://gist.github.com/douglasmiranda/5127251 . I found that before posting because ColdSpeed's regex suggestion in the comment made me wonder if I were leading you down the wrong path.

So something like

import numbers 
def recursively_search(object_from_json, target):
    if isinstance(object_from_json, (basestring, numbers.Number)):
        return object_from_json==target # the recursion base cases
    elif isinstance(object_from_json, list):
        for element in list:
            if recursively_search(element, target):
                return True # quit at first match
    elif isinstance(object_from_json, dict):
        if target in object_from_json:
            return True # among the keys
        else:
            for value in object_from_json.values():
                if recursively_search(value, target):
                    return True # quit upon finding first match
    else:
        print ("recursively_search() did not anticipate type ",type(object_from_json))
        return False
    return False # no match found among the list elements, dict keys, nor dict values
Bennett Brown
  • 5,234
  • 1
  • 27
  • 35