从键路径获取嵌套字典值

28

使用键路径从嵌套字典中获取值,以下是dict

json = {
    "app": {
        "Garden": {
            "Flowers": {
                "Red flower": "Rose",
                "White Flower": "Jasmine",
                "Yellow Flower": "Marigold"
            }
        },
        "Fruits": {
            "Yellow fruit": "Mango",
            "Green fruit": "Guava",
            "White Flower": "groovy"
        },
        "Trees": {
            "label": {
                "Yellow fruit": "Pumpkin",
                "White Flower": "Bogan"
            }
        }
    }
方法的输入参数是由用点分隔的关键路径,从关键路径 =“app.Garden.Flowers.white Flower”,需要打印出“茉莉花”。我迄今为止的代码:

方法的输入参数是通过用点分隔的关键路径确定的。对于给定的关键路径 =“app.Garden.Flowers.white Flower”,需要打印出“Jasmine”。我的代码如下:

import json
with open('data.json') as data_file:    
  j = json.load(data_file)


def find(element, JSON):     
  paths = element.split(".")  
  # print JSON[paths[0]][paths[1]][paths[2]][paths[3]]
  for i in range(0,len(paths)):
    data = JSON[paths[i]]
    # data = data[paths[i+1]]
    print data



find('app.Garden.Flowers.White Flower',j)
9个回答

68

这是一个fold的实例。你可以像这样简洁地编写它:

from functools import reduce
import operator

def find(element, json):
    return reduce(operator.getitem, element.split('.'), json)

或者更符合 Python 风格(因为由于可读性差而 reduce() 不受欢迎),可以这样写:

def find(element, json):
    keys = element.split('.')
    rv = json
    for key in keys:
        rv = rv[key]
    return rv

j = {"app": {
    "Garden": {
        "Flowers": {
            "Red flower": "Rose",
            "White Flower": "Jasmine",
            "Yellow Flower": "Marigold"
        }
    },
    "Fruits": {
        "Yellow fruit": "Mango",
        "Green fruit": "Guava",
        "White Flower": "groovy"
    },
    "Trees": {
        "label": {
            "Yellow fruit": "Pumpkin",
            "White Flower": "Bogan"
        }
    }
}}
print find('app.Garden.Flowers.White Flower', j)

1
不必使用lambda定义自己的item-getter函数,您可以import operator并使用operator.getitem - Christian Long
2
如果您想通过一个整数索引来引用一个条目(例如app.Garden.Flowers.0),您可以像这样更改您的for循环:try: rv = rv[int(key)] except: rv = rv[key] – n.r. 4分钟前 编辑 - n.r.
1
此解决方案不支持列表,如果您将Flowers更改为列表,则会出现错误。 - AviC
1
@n.r.提出了一个不错的建议,但我不喜欢使用try-except来控制流程。如果type(rv) is list,你也可以使用三元运算符并转换键。 - TastyWheat

13

我曾经遇到过类似的情况,并发现了这个dpath模块。非常好用。


非常酷。然而,我希望有一种方法可以做类似于s = "%('red/buggy/bumpers')s" % { "red": { "buggy": { "bumpers":"foo" }}}的事情。但我想使用适当的模板语言比更改字符串上的%运算符的行为更好。 - Michael Scheper
我的意思是,您可以确定以“{”开头并以“}”结尾的子字符串是一个字典。然后将该子字符串通过eval()方法运行,将其转换为字典对象。接下来,拆分您的“/”分隔路径,并使用for循环应用键。 - flying_loaf_3

6
我建议您使用python-benedict,这是一个Python字典子类,具有完整的键路径支持和多个实用方法。
您只需要将现有的字典转换即可:
d = benedict(json)
# now your keys support dotted keypaths
print(d['app.Garden.Flower.White Flower'])

这里是库和文档: https://github.com/fabiocaccamo/python-benedict

注意: 我是这个项目的作者。


3
你的代码极度依赖于键名中不含点号,虽然你可能能够控制,但不一定。我建议使用通用解决方案,使用元素名称列表,然后通过拆分带有点号的键名列表来生成该列表。
class ExtendedDict(dict):
    """changes a normal dict into one where you can hand a list
    as first argument to .get() and it will do a recursive lookup
    result = x.get(['a', 'b', 'c'], default_val)
    """
    def multi_level_get(self, key, default=None):
        if not isinstance(key, list):
            return self.get(key, default)
        # assume that the key is a list of recursively accessible dicts
        def get_one_level(key_list, level, d):
            if level >= len(key_list):
                if level > len(key_list):
                    raise IndexError
                return d[key_list[level-1]]
            return get_one_level(key_list, level+1, d[key_list[level-1]])

        try:
            return get_one_level(key, 1, self)
        except KeyError:
            return default

    get = multi_level_get # if you delete this, you can still use the multi_level-get

一旦你有了这个类,就可以轻松地转换你的字典并获得"Jasmine":

json = {
        "app": {
            "Garden": {
                "Flowers": {
                    "Red flower": "Rose",
                    "White Flower": "Jasmine",
                    "Yellow Flower": "Marigold"
                }
            },
            "Fruits": {
                "Yellow fruit": "Mango",
                "Green fruit": "Guava",
                "White Flower": "groovy"
            },
            "Trees": {
                "label": {
                    "Yellow fruit": "Pumpkin",
                    "White Flower": "Bogan"
                }
            }
        }
    }

j = ExtendedDict(json)
print j.get('app.Garden.Flowers.White Flower'.split('.'))

您将获得:

Jasmine

与普通字典的get()一样,如果您指定的键(列表)在树中不存在,则会返回None,并且您可以指定第二个参数作为返回值而不是None


2

一句话简述:

from functools import reduce

a = {"foo" : { "bar" : "blah" }}
path = "foo.bar"

reduce(lambda acc,i: acc[i], path.split('.'), a)

2

选项1:思科的pyats库[这是一个C扩展]

  • 快速且超级快(如果需要,请使用timeit进行测量)
  • 类似于Javascript的用法[方括号查找,点查找,组合查找]
  • 缺失键的点查找会引发AttributeError,而方括号或默认的Python字典查找则会引发KeyError。
pip install pyats pyats-datastructures pyats-utils

from pyats.datastructures import NestedAttrDict
item = {"specifications": {"os": {"value": "Android"}}}
path = "specifications.os.value"
x = NestedAttrDict(item)
print(x[path])# prints Android
print(x['specifications'].os.value)# prints Android
print(x['specifications']['os']['value'])#prints Android
print(x['specifications'].os.value1)# raises Attribute Error

选项2:pyats.utils chainget

  • 超级快速(如果需要,请使用timeit进行测量)
from pyats.utils import utils
item = {"specifications": {"os": {"value": "Android"}}}
path = "specifications.os.value"
path1 = "specifications.os.value1"
print(utils.chainget(item,path))# prints android (string version)
print(utils.chainget(item,path.split('.')))# prints android(array version)
print(utils.chainget(item,path1))# raises KeyError

选项 3:不使用外部库的 Python

  1. 与 lambda 相比速度更快。
  2. 不需要像 lambda 和其他情况一样单独处理错误。
  3. 可读性好且简洁,可以成为项目中的一个工具函数或辅助函数。
from functools import reduce
item = {"specifications": {"os": {"value": "Android"}}}
path1 = "specifications.family.value"
path2 = "specifications.family.value1"

def test1():
    print(reduce(dict.get, path1.split('.'), item))

def test2():
    print(reduce(dict.get, path2.split('.'), item))

test1() # prints Android
test2() # prints None

1

非常接近。你需要(就像在你的评论中所说的那样)递归遍历主JSON对象。你可以通过存储最外层键/值的结果来实现这一点,然后使用它来获取下一个键/值,以此类推,直到没有路径为止。

def find(element, JSON):     
  paths = element.split(".")
  data = JSON
  for i in range(0,len(paths)):
    data = data[paths[i]]
  print data

然而,您仍需要注意KeyErrors。


1
编写了一个与字典中的列表一起使用的函数。
d = {'test': [
    {'value1': 'val'},
    {'value1': 'val2'}]}


def find_element(keys: list, dictionary: dict):
    rv = dictionary
    if isinstance(dictionary, dict):
        rv = find_element(keys[1:], rv[keys[0]])
    elif isinstance(dictionary, list):
        if keys[0].isnumeric():
            rv = find_element(keys[1:], dictionary[int(keys[0])])
    else:
        return rv
    return rv


val = find_element('test.1.value1'.split('.'), d)

-1

数据:

data = {
    "data": {
        "author_id": "1",
        "text": "hi msg",
        "attachments": {
            "media_keys": [
                "3_16"
            ]
        },
        "id": "2",
        "edit_history_tweet_ids": [
            "2"
        ]
    },
    "includes": {
        "media": [
            {
                "media_key": "3_16",
                "height": 500,
                "type": "photo",
                "width": 500,
                "url": "https://pbs.twimg.com/media/xxxxxx.png"
            }
        ],
        "users": [
            {
                "id": "1",
                "name": "name1",
                "username": "username1"
            }
        ]
    }
}

功能:

    def get_value_from_dict(dic_obj, keys: list, default):
        """
        get value from dict with key path.
        :param dic_obj: dict
        :param keys: dict key
        :param default: default value
        :return:
        """
        if not dic_obj or not keys:
            return default
    
        pre_obj = dic_obj
        for key in keys:
            t = type(pre_obj)
            if t is dict:
                pre_obj = pre_obj.get(key)
            elif (t is list or t is tuple) and str(key).isdigit() and len(pre_obj) > int(key):
                pre_obj = pre_obj[int(key)]
            else:
                return default
        return pre_obj

测试:

    print('media_key:', get_value_from_dict(data, 'data.attachments.media_keys'.split('.'), None))
    print('username:', get_value_from_dict(data, 'includes.users.0.username'.split('.'), None))


    media_key: ['3_16']
    username: username1

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