【问题标题】:Build a nested data class with lists使用列表构建嵌套数据类
【发布时间】:2022-01-20 19:28:33
【问题描述】:

我正在处理 JSON API 调用,我想使用 python 数据类来构建它。 由于我是初学者,因此解决方案可能很麻烦 - 在这种情况下 - 请提供更优雅的解决方案。

所以我希望我的 JSON 请求正文如下所示:

{
    "projectFilter": {
        "project": "all",
        "statuses": ["Finished"]
    },
    "currencyId": 0,
    "columns": [
        {"columnType": {
            "kind": "CostActual"
            }
        }
    ]
}

我已将其建模为嵌套数据类,以便将来更改 API 调用。 不确定这是否是一个好方法——正如我所说——这里是初学者。

import json
from dataclasses import dataclass, field
from typing import List, Dict

@dataclass
class _project_filter():
    project : str = 'all'
    statuses : List = field(default_factory=lambda: ['Finished'])

@dataclass
class _column_type():
    columnType : Dict = field(default_factory=lambda: {'kind' : 'CostActual'})

@dataclass
class project():
    projectFilter: _project_filter = _project_filter()
    currencyId : int = NULL
    columns : List[_column_type] = field(default_factory=_column_type)

    def to_json(self):
        return json.dumps(self.__dict__, default=lambda x: x.__dict__, ensure_ascii=False)

我在调用.to_json() 方法时得到以下输出:

{
    "projectFilter": {
        "project": "all",
        "statuses": ["Finished"]
    },
    "currencyId": 0,
    "columns": {
        "columnType": {"kind": "CostActual"}
    }
}

如您所见,我没有得到_column_type 类型的数组。 我认为它会起作用,因为我有columns : List[_column_type] = field(default_factory=_column_type) 虽然我将字段columns 指定为_column_typelist,并且我使用了默认对象,因为我使用了default_factory 参数。

【问题讨论】:

  • 提示 1:_column_type() 是否产生 List[_column_type]?提示 2:仔细考虑example in the documentation
  • 也就是说,按照初学者的标准,您的一般方法看起来相当复杂。我非常喜欢“鸭子打字工作得很好,谢谢,我不想花时间在注释上”阵营,但这让我有理由更认真地看待新的dataclass 支持。

标签: python json python-3.x python-dataclasses


【解决方案1】:

我建议查看dataclass-wizard 库,因为它似乎非常适合这个用例。

用于序列化目的的默认大小写转换是 camelCase,所以好消息是在将实例序列化为 JSON 时,您无需传递任何额外的配置即可获得所需的结果。 p>


我首先将示例 JSON 输入通过管道传输到包含的 CLI utility 以生成数据类模式,并最终得到如下数据类模型结构。请注意,我进入并为一些字段添加了默认值,类似于您在上面的方式。

from __future__ import annotations

from dataclasses import dataclass, field

from dataclass_wizard import JSONWizard


@dataclass
class Project(JSONWizard):
    project_filter: ProjectFilter
    currency_id: int
    columns: list[Column]


@dataclass
class ProjectFilter:
    project: str = 'all'
    statuses: list[str] = field(default_factory=lambda: ['Finished'])


@dataclass
class Column:
    column_type: ColumnType | None = None


@dataclass
class ColumnType:
    kind: str = 'CostActual'

然后我做了一个快速测试以确认我能够使用数据类架构加载/转储 JSON 数据:

def main():
    string = """
    {
        "projectFilter": {
            "project": "all"
        },
        "currencyId": 0,
        "columns": [
            {"columnType": {}}
        ]
    }"""

    p = Project.from_json(string)
    print(repr(p))

    print(p.to_json())

    # True
    assert p == p.from_json(p.to_json())


if __name__ == '__main__':
    main()

输出:

Project(project_filter=ProjectFilter(project='all', statuses=['Finished']), currency_id=0, columns=[Column(column_type=ColumnType(kind='CostActual'))])
{"projectFilter": {"project": "all", "statuses": ["Finished"]}, "currencyId": 0, "columns": [{"columnType": {"kind": "CostActual"}}]}

【讨论】:

  • 评论只是重复已经存在的内容是无用的,当在一种情况下它相同时(Data?)它会变得烦人。
  • @trincot 好点。这些实际上是自动生成的 cmets,并且类名实际上是 Data 但后来我将其重命名为 Project 以更好地描述数据。我会删除 cmets,当然你是对的。
【解决方案2】:

默认因子只创建一个 _column_type 类型的对象,而不是一个列表。你可以改变它

@dataclass
class project():
    projectFilter: _project_filter = _project_filter()
    currencyId : int = NULL
    columns : List[_column_type] = field(default_factory=lambda:[_column_type()])

所以使用一个未命名的函数,一个 lambda,它创建一个包含该类型的单个对象的列表。

作为旁注,在 Python 中,类以 pascal 类型命名,因此 HelloWorld,而不是蜗牛大小写为 hello_world。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-04-20
    • 2021-09-20
    • 2019-08-06
    相关资源
    最近更新 更多