将JSON编组成强类型Python对象的最佳方法是什么?这种对象提供了更多的数据保证,而不仅仅是转换为类似dict
的对象。
我有一些JSON从外部API看起来像这样:
{
"Name of Event": {
"start": "2021-01-01 00:00:00",
"event_type": 1
},
"Another Event": {
"start": "2021-01-01 00:00:00",
"event_type": 2,
}
}
(重要的是要知道我正在使用的实际JSON要复杂得多(并且嵌套得很深),但基本上它是一个结构非常良好的东西,具有已知的类型。)
我可以做一些简单的事情:
for name, event in json.loads(data):
do_things(name, event['start'], event['event_type'])
,但这感觉相当模糊,并没有给我的程序提供太多的类型检查,无论是在写时还是运行时。
在我处理这个JSON的代码中,我想使用正确类型的东西。但是我不想写一大堆样板文件。
我可以做一些非常非常明确的事情,比如:
DATE_FORMAT = "%y-%m-%d %H:%M:%S"
class EventType:
FREE_FOR_ALL = 1
CLOSED_REGISTRATION = 2
class Event:
__slots__ = ["start", "event_type"]
start: datetime.datetime
event_type: EventType
def __init__(self, start, event_type):
self.start = datetime.datetime.strptime(start, DATE_FORMAT)
self.event_type = EventType(event_type)
def __str__(self):
return str(self.__dict__)
def __repr__(self):
return str(self.__dict__)
APIResponse = Dict[str, Event]
for name, raw_event in data:
event = Event(**data)
do_things(name, event)
到目前为止,这是好的,但是一旦你有十几个类,每个类有十几个属性,它就开始看起来像很多样板文件。特别是,我觉得我定义了每个属性两次,违反了DRY。一次在班级,一次在__init__
。
(我也有点担心这有点"脆弱")对于像API在任何给定的enum中添加新选项等情况,但这是一个较小的问题,因为我希望API的更改需要更改我的代码。
我想知道是否有任何神奇的我可以使用,这将使它,所以我只需要定义每个字段在一个地方,但仍然得到良好的类型检查和运行时保证,数据是在我期望的形式?
我看了看dataclasses
,但似乎我无法干扰JSON的简单字符串/int输入到枚举,日期时间等。我可以使用InitVar
将许多输入标记为"__init__
only"然后使用__post_init__
用我的数据的强类型版本填充其他具有不同名称的字段。但是"权利"大多数字段的名称都是JSON中已经存在的名称(我不想写event.event_type_typed_version
)。
理想情况下我会这样写:
@magic_annotation
class Event:
start: datetime.datetime
event_type: EventType
for name, raw_event in data:
event = Event(**data)
do_things(name, event)
,不需要其他任何东西。magic_annotation
存在吗?有没有完全不同的方法来解决这个问题?
听起来你在找pydantic。
from datetime import datetime
from enum import Enum
from pydantic import BaseModel
class EventType(Enum):
FREE_FOR_ALL = 1
CLOSED_REGISTRATION = 2
class Event(BaseModel):
start: datetime
event_type: EventType
event = Event.parse_obj({
"start": "2021-01-01 00:00:00",
"event_type": 1
})
print(repr(event))
# Event(start=datetime.datetime(2021, 1, 1, 0, 0), event_type=<EventType.FREE_FOR_ALL: 1>)
Pydantic会根据类属性的类型注释自动转换输入。对于日期时间,它支持标准的ISO 8601格式。对于枚举,它自动从枚举值进行转换。点击这里查看详情!(没有关联,只是一个粉丝)