如何将嵌套 JSON 解压到 Python 数据类中

Zac*_*son 9 json python-3.x

数据类示例:

@dataclass
class StatusElement:
    status: str
    orderindex: int
    color: str
    type: str


@dataclass
class List:
    id: int 
    statuses: List[StatusElement]
Run Code Online (Sandbox Code Playgroud)

JSON 示例:

json = {
  "id": "124",
  "statuses": [
    {
      "status": "to do",
      "orderindex": 0,
      "color": "#d3d3d3",
      "type": "open"
    }]
}
Run Code Online (Sandbox Code Playgroud)

我可以像这样解压 JSON:

object = List(**json)
Run Code Online (Sandbox Code Playgroud)

但我不确定如何将状态解压到状态对象中并附加到statusesList 对象的列表中?我确信我需要以某种方式循环它,但不确定如何将其与拆包结合起来。

rv.*_*tch 19

Pythondataclasses是一个很棒的模块,但不幸的是它不能处理的事情之一是将 JSON 对象解析为嵌套数据类结构。

对此存在一些解决方法:

  • 您可以推出自己的 JSON 解析帮助器方法,例如from_json将 JSON 字符串转换为List具有嵌套数据类的实例。
  • 您可以利用现有的 JSON 序列化库。例如,pydantic是支持此用例的流行的一个。

下面是一个使用dataclass-wizard库的示例,它足以满足您的用例。它更轻pydantic,巧合的是也更快一点。它还支持自动大小写转换和类型转换(例如str到 annotated int

下面的例子:

from dataclasses import dataclass
from typing import List as PyList

from dataclass_wizard import JSONWizard


@dataclass
class List(JSONWizard):
    id: int
    statuses: PyList['StatusElement']
    # on Python 3.9+ you can use the following syntax:
    #   statuses: list['StatusElement']


@dataclass
class StatusElement:
    status: str
    order_index: int
    color: str
    type: str


json = {
  "id": "124",
  "statuses": [
    {
      "status": "to do",
      "orderIndex": 0,
      "color": "#d3d3d3",
      "type": "open"
    }]
}


object = List.from_dict(json)

print(repr(object))
# List(id=124, statuses=[StatusElement(status='to do', order_index=0, color='#d3d3d3', type='open')])
Run Code Online (Sandbox Code Playgroud)

免责声明:我是这个库的创建者(和维护者)。


最新版本的dataclass-wizard. 使用起来非常简单;使用上面的相同示例,但我已JSONWizard完全删除其中的用法。请记住确保您不asdictdataclasses模块导入,尽管我猜这应该恰好可以工作。

这是上面的修改版本,没有类继承

from dataclasses import dataclass
from typing import List as PyList

from dataclass_wizard import fromdict, asdict


@dataclass
class List:
    id: int
    statuses: PyList['StatusElement']


@dataclass
class StatusElement:
    status: str
    order_index: int
    color: str
    type: str


json = {
  "id": "124",
  "statuses": [
    {
      "status": "to do",
      "orderIndex": 0,
      "color": "#d3d3d3",
      "type": "open"
    }]
}

# De-serialize the JSON dictionary into a `List` instance.
c = fromdict(List, json)

print(c)
# List(id=124, statuses=[StatusElement(status='to do', order_index=0, color='#d3d3d3', type='open')])

# Convert the instance back to a dictionary object that is JSON-serializable.
d = asdict(c)

print(d)
# {'id': 124, 'statuses': [{'status': 'to do', 'orderIndex': 0, 'color': '#d3d3d3', 'type': 'open'}]}
Run Code Online (Sandbox Code Playgroud)

此外,这里还提供了与dacite. 我之前不知道这个库,但它也非常容易使用(而且也不需要从任何类继承)。然而,从我个人的测试来看——使用 Python 3.9.1 的 Windows 10 Alienware PC——dataclass-wizard似乎在反序列化过程中总体表现要好得多。

from dataclasses import dataclass
from timeit import timeit
from typing import List

from dacite import from_dict

from dataclass_wizard import JSONWizard, fromdict


data = {
    "id": 124,
    "statuses": [
        {
            "status": "to do",
            "orderindex": 0,
            "color": "#d3d3d3",
            "type": "open"
        }]
}


@dataclass
class StatusElement:
    status: str
    orderindex: int
    color: str
    type: str


@dataclass
class List:
    id: int
    statuses: List[StatusElement]


class ListWiz(List, JSONWizard):
    ...


n = 100_000

# 0.37
print('dataclass-wizard:            ', timeit('ListWiz.from_dict(data)', number=n, globals=globals()))

# 0.36
print('dataclass-wizard (fromdict): ', timeit('fromdict(List, data)', number=n, globals=globals()))

# 11.2
print('dacite:                      ', timeit('from_dict(List, data)', number=n, globals=globals()))


lst_wiz1 = ListWiz.from_dict(data)
lst_wiz2 = from_dict(List, data)
lst = from_dict(List, data)

# True
assert lst.__dict__ == lst_wiz1.__dict__ == lst_wiz2.__dict__
Run Code Online (Sandbox Code Playgroud)

  • 这看起来真的很光滑。我看过 pydantic,对于我想做的事情来说似乎有点沉重。我得试试这个图书馆。谢谢! (2认同)

Chr*_*per 7

我花了几个小时研究这方面的选择。没有原生 Python 功能可以执行此操作,但有一些第三方包(编写于 2022 年 11 月):

  • marshmallow_dataclass具有此功能(您无需在项目中以任何其他身份使用棉花糖)。它提供了良好的错误消息,并且该包得到了积极的维护。我使用了它一段时间,然后遇到了我认为将大型复杂 JSON 解析为深度嵌套数据类时出现的错误,然后不得不放弃。
  • dataclass-wizard易于使用并且专门解决了这个用例。它有优秀的文档。一个显着的缺点是,如果尝试与数据类的联合进行匹配,它不会自动尝试为给定的 JSON 找到正确的匹配(请参阅https://dataclass-wizard.readthedocs.io/en/latest/common_use_cases/ dataclasses_in_union_types.html)。相反,它会要求您向输入 JSON 添加“标签键”,这是一个强大的解决方案,但如果您无法控制输入 JSON,则可能无法实现。
  • dataclass-json与 类似dataclass-wizard,并且同样不会尝试匹配联合内的正确数据类。
  • dacite是我暂时决定的选择。它具有与 类似的功能marshmallow_dataclass,至少在 JSON 解析方面如此。错误消息明显不如 清晰marshmallow_dataclass,但稍微抵消了这一点,如果您在错误发生时更容易找出问题所在pdb- 内部结构非常清晰,您可以尝试看看出了什么问题。根据其他人的说法,它相当慢,但这在我的情况下不是问题。


bal*_*man 6

一个“更清洁”的解决方案(在我看来)。使用英安岩

不需要继承什么

from dataclasses import dataclass
from typing import List
from dacite import from_dict

data = {
    "id": 124,
    "statuses": [
        {
            "status": "to do",
            "orderindex": 0,
            "color": "#d3d3d3",
            "type": "open"
        }]
}


@dataclass
class StatusElement:
    status: str
    orderindex: int
    color: str
    type: str


@dataclass
class List:
    id: int
    statuses: List[StatusElement]


lst: List = from_dict(List, data)
print(lst)
Run Code Online (Sandbox Code Playgroud)

输出

List(id=124, statuses=[StatusElement(status='to do', orderindex=0, color='#d3d3d3', type='open')])
Run Code Online (Sandbox Code Playgroud)

  • 只是注意一下,但我把关于继承不必要的建议放在心上 - 最新版本的“dataclass-wizard”现在应该支持“fromdict”,因此常规数据类也应该可以工作。我更新了上面的答案。 (2认同)