在嵌套字典中搜索键

4
我在Python中有一个JSON对象,表示为嵌套的字典列表。(字典的某些值本身也是字典,以此类推。)
我希望能够在所有嵌套字典结构的分支上搜索关键字。当我找到所需的关键字时,我想要返回导致它的完整关键字路径。
例如:我正在寻找具有“特殊地址键”的“特殊代理人”,但并非所有特殊代理人都具有它,并且那些拥有它的代理人在其JSON中的路径不一致。
所以我搜索关键字Special Address code。结果应该返回:
/'People'/'SpecialAgents'/'007'/'Special Address code'/  

那么我就能以这种方式获取它的信息:

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

请注意,这类似于此问题,但我需要找到每个键的完整路径。

听起来你需要一个类似于XPath的JSON等价物。这个问题有一些例子。 - Daniel Roseman
如果有多个相同的键怎么办? - Padraic Cunningham
参见:https://dev59.com/x1vUa4cB1Zd3GeqPy_1D https://dev59.com/x1vUa4cB1Zd3GeqPy_1D#16508328 - dreftymac
2个回答

13

你需要进行递归搜索。

你可以定义一个函数来深度搜索你的输入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 

我们可以使用这个类似的SO问题中的JSON示例来测试递归搜索:

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']

+1 这个可行。不错。我在对象中搜索“id”,并测试了它,来自这个问题。对于它的工作原理和来源有什么评论吗?(你是从头开始编写所有这些代码的吗?) - LondonRob
1
@LondonRob 这个函数迭代键并存储当前路径的状态。它基于 yield return(简化了解决方案)编写,按照 OP 的描述从头开始编写。 - Jossef Harush Kadouri
我稍微编辑一下,展示一个更完整的例子,你介意吗?这样可以更好地展示你的工作,比目前的单个结果更好。 - LondonRob
@LondonRob,随意改进这个答案(以及该网站上的任何其他答案:P) - Jossef Harush Kadouri

3

您需要搜索一棵树。以下是最简单的方法。

它可以进行改进 - 例如,最好使用None作为默认参数值,而不是某个对象。此外,这是深度优先搜索 - 如果您只想获得一个结果,那么广度优先搜索更好(如果您不了解这些术语,请在维基百科上阅读有关它们的内容)。

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"

这是仅适用于Python3的语法,您也不需要调用.keys来迭代字典键。 - Padraic Cunningham
告诉你吧,这个可以做一些增强:P 另一个丑陋的事情是使用isinstance(),但这不是重点。此外,我认为“仅限python3”并不是坏事 - 我强烈支持迁移到新行而不是使用p2.7。 - Filip Malczak
直到Python的仙女敲响她的手指,使所有编写的Python代码都与Python 3兼容之前,我想Python2代码仍然需要被编写。我认为isinstance()没有任何问题,这是必要的,不像keys ;) - Padraic Cunningham
已修复。无论如何,唯一的区别是在p2中,您应该使用for x in X:yield x而不是yield from X。更多信息请参见:https://dev59.com/X2Mm5IYBdhLWcg3wfu82#17581397,如果有人需要查看。 - Filip Malczak

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接