python-3.x 如何最好地将json或dict转换为对象?

rseugnpd  于 2023-01-14  发布在  Python
关注(0)|答案(1)|浏览(232)

假设我有一些数据(可以是jsondict),它们以某种相对复杂的方式任意构造,如下面示例代码中的example字典。

from dataclasses import asdict, dataclass, field
from typing import Any, List

@dataclass
class NameValue:
    name: str
    value: Any

@dataclass
class Category:
    filters: List[NameValue] = field(default_factory=list)
    description: Any = field(default=None)

@dataclass
class Container:
    id: int
    categories: List[Category] = field(default_factory=list)

    def __post_init__(self):
        if self.categories:
            self.categories = [Category(**category) for category in self.categories]

    def do_stuff(self, arg: str) -> None:
        """Do stuff with arg."""

        print(f"Doing stuff with {arg}")
        return None

def main():

    example = {
        "id": 1,
        "categories": [
            {
                "filters": [
                    {"name": "two", "value": 2},
                    {"name": "three", "value": 3},
                    {"name": "four", "value": 4},
                ],
                "description": "Kick it!",
            },
            {
                "filters": [
                    {"name": "five", "value": 5},
                    {"name": "six", "value": 6},
                    {"name": "seven", "value": 7},
                    {"name": "eight", "value": 8},
                ],
                "description": "Something to appreciate.",
            },
        ],
    }

    container = Container(**example)
    container.do_stuff("this thing")

    assert asdict(container) == example

    return None

if __name__ == "__main__":
    main()

我希望能够轻松地处理对象形式的数据,这样我就能获得oop的好处。
我的想法是通过将一个解压缩的字典传递给Container类来生成一个对象,操作这些对象,然后根据需要将它们返回给dict s和/或json字符串。
脚本在这个例子中运行良好,但是考虑到我在使用这种方法时遇到的一些问题(性能慢、默认值混乱等),我总觉得可能有更好的(即更有效、更像Python的)方法来完成这个任务。
提前感谢您的任何有益建议。

nue99wik

nue99wik1#

这类事情有时被称为“装箱”--用它来谷歌一个包,你可以找到一些库,可以做准备安装在Pypi上。
但是,您的方法中的“性能”不应该是一个太大的问题--但是一些库可能会保留底层数据结构,并延迟获取所请求的字段--如果您正在处理成千上万个这样的对象,而当它们是Python对象时,实际上只使用了一小部分字段,那么这可能会产生影响--否则就不会了:为了检索Dict中的所有字段,其内容无论如何都必须被复制。
另外,如果你不想使用第三方库,那么用Python编写一些递归代码,将JSON解析成任意对象,这在某种程度上是很容易做到的--下面的代码就可以工作。如果你想解析成特定的预定义类,或者从Python文件中的预定义类解析出来,你必须采用这里介绍的手动方法。
这将适用于任意JSON结构,而无需在Python中声明任何类:

from collections.abc import Mapping, Sequence

class Box:
    def __init__(self, _data):
        self._data = _data
    def __dir__(self):
        return sorted(self._data.keys()) if isinstance(self._data, dict) else dir(self._data)
    def __getattr__(self, name):
        if not isinstance(self._data, Mapping):
            return self._data
        try:
            value = self._data[name]
        except KeyError:
            raise AttributeError(name)
        if isinstance(value, (Mapping, Sequence)) and not isinstance(data, (str, bytes)) :
            value = type(self)(value)
        return value

    def __getitem__(self, key):
        # provided mostly to enable access to list elements
        value = self._data[key]
        if isinstance(value, (Mapping, Sequence))  and not isinstance(data, (str, bytes)):
            return type(self)(value)
    def __len__(self):
        return len(self._data)
    def __setattr__(self, name, value):
        if name.startswith("_"):
            return super().__setattr__(name, value)
        if isinstance(value, Box):
            value = value._data
        self._data[name] = value
    def __repr__(self):
        return f"Box({self._data!r})"

下面是示例数据如何在交互式环境中与此类一起工作:

In [22]: ee = Box(example)

In [23]: dir(ee)
Out[23]: ['categories', 'data', 'id']

In [24]: ee.data
Out[24]: Box({'id': 1, 'categories': [{'filters': [{'name': 'two', 'value': 2}, {'name': 'three', 'value': 3}, {'name': 'four', 'value': 4}], 'description': 'Kick it!'}, {'filters': [{'name': 'five', 'value': 5}, {'name': 'six', 'value': 6}, {'name': 'seven', 'value': 7}, {'name': 'eight', 'value': 8}], 'description': 'Something to appreciate.'}], 'data': {...}})

In [25]: ee.categories[0].filters[0].name
Out[25]: Box('two')

相关问题