Поиск ключей во вложенном словаре

У меня есть объект JSON в Python, представленный как вложенный список словарей. (Некоторые из значений словаря - сами словари и т.д.)

Я хочу, чтобы иметь возможность искать ключ во всех ветвях этой вложенной структуры словаря.
Когда я нахожу ключ, я хочу, чтобы он смог вернуть полный ключевой путь, который ведет к нему.

Например: Я ищу "специальные агенты", у которых есть "специальный адресный ключ", но не все его специальные агенты, и те, которые имеют его в непоследовательных путях в своем JSON.

Итак, я ищу ключ Special Address code. Результат должен вернуться:

/'People'/'SpecialAgents'/'007'/'Special Address code'/  

Таким образом, я смогу достичь своей информации таким образом:

json_obj['People']['SpecialAgents']['007']['Special Address code']

Обратите внимание, что это похоже на этот вопрос, но мне нужен полный путь к каждому экземпляру найденного ключа.

Ответ 1

Вам нужен рекурсивный поиск.

Вы можете определить функцию для глубокого поиска в вашем входе json:

def find_in_obj(obj, condition, path=None):

    if path is None:
        path = []    

    # In case this is a list
    if isinstance(obj, list):
        for index, value in enumerate(obj):
            new_path = list(path)
            new_path.append(index)
            for result in find_in_obj(value, condition, path=new_path):
                yield result 

    # In case this is a dictionary
    if isinstance(obj, dict):
        for key, value in obj.items():
            new_path = list(path)
            new_path.append(key)
            for result in find_in_obj(value, condition, path=new_path):
                yield result 

            if condition == key:
                new_path = list(path)
                new_path.append(key)
                yield new_path 

Затем мы можем использовать пример JSON в этом аналогичном вопросе SO для проверки рекурсивного поиска:

In [15]: my_json = { "id" : "abcde",
   ....:   "key1" : "blah",
   ....:   "key2" : "blah blah",
   ....:   "nestedlist" : [ 
   ....:     { "id" : "qwerty",
   ....:       "nestednestedlist" : [ 
   ....:         { "id" : "xyz",
   ....:           "keyA" : "blah blah blah" },
   ....:         { "id" : "fghi",
   ....:           "keyZ" : "blah blah blah" }],
   ....:       "anothernestednestedlist" : [ 
   ....:         { "id" : "asdf",
   ....:           "keyQ" : "blah blah" },
   ....:         { "id" : "yuiop",
   ....:           "keyW" : "blah" }] } ] } 

Найдите каждый экземпляр ключа "id" и верните полный путь, который доставит нас:

In [16]: for item in find_in_obj(my_json, 'id'):
   ....:     print item
   ....:     
['nestedlist', 0, 'nestednestedlist', 0, 'id']
['nestedlist', 0, 'nestednestedlist', 1, 'id']
['nestedlist', 0, 'id']
['nestedlist', 0, 'anothernestednestedlist', 0, 'id']
['nestedlist', 0, 'anothernestednestedlist', 1, 'id']
['id']

Ответ 2

Вам нужно найти дерево. Здесь самый простой способ сделать это.

Он может быть расширен - например, лучше использовать None в качестве значения arg по умолчанию вместо некоторого объекта. Кроме того, это первый поиск глубины - вы можете получить только один результат и что, когда ширина первого поиска лучше (читайте об этих терминах в wikipedia, если вы их не знаете).

import json

example_json = """{
 "someList" : [
  {
   "x": {
    "y": {
     "z": "Some value"
    }
   }
  }, 
  {
   "x": {
    "y": {
     "a": "Wrong key"
    }
   }
  }
 ]
}
"""

struct = json.loads(example_json)

def find_all_with_key(wanted_key, tree, path=tuple()):
    if isinstance(tree, list):
        for idx, el in enumerate(tree):
            yield from find_all_with_key(wanted_key, el, path+(idx,))
    elif isinstance(tree, dict):
        for k in tree:
            if k == wanted_key:
                yield path +(k, )
        # you can add order of width-search by sorting result of tree.items()
        for k, v in tree.items(): 
            yield from find_all_with_key(wanted_key, v, path+(k,))

def retrieve(tree, path):
    for p in path:
        tree = tree[p]
    return tree

result = list(find_all_with_key("z", struct))
expected = [ ("someList", 0, "x", "y", "z") ]

assert result == expected
assert retrieve(struct, result[0]) == "Some value"