简体   繁体   English

如何使用类型信息和枚举将 JSON 转换为 object?

[英]How to convert JSON to an object with type information and enums?

What's the best way to marshal some JSON into a strongly-typed Python object that offers more guarantees about the data than just converting into a dict -alike?将一些 JSON 编组为强类型 Python object 的最佳方法是什么?它提供更多关于数据的保证,而不仅仅是转换成一个dict -a

I have some JSON from an external API that looks like this:我有一些来自外部 API 的 JSON 看起来像这样:

{
    "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,
    }
}

(It's important to know that the actual JSON I'm working with is a lot more complicated (and deeply nested) but basically it's a very well-structured thing with known types all the way down.) (重要的是要知道我正在使用的实际 JSON 要复杂得多(并且嵌套很深),但基本上它是一个结构非常好的东西,一直都是已知类型。)

I can do something as simple as:我可以做一些简单的事情:

for name, event in json.loads(data):
     do_things(name, event['start'], event['event_type'])

but this feels fairly woolly and doesn't give my program much to go on in terms of typechecking either at write time or run time.但这感觉相当模糊,并且在写入时或运行时的类型检查方面并没有给我的程序太多 go 。

In the code where I handle this JSON I'd like to work with something with the right types.在我处理这个 JSON 的代码中,我想使用正确类型的东西。 But I don't want to write a ton of boilerplate.但我不想写一大堆样板文件。

I can do something very very explicit like:我可以做一些非常明确的事情,比如:

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)

This is fine as far as it goes but once you have a dozen classes with a dozen attributes each it starts to look like a lot of boilerplate.就目前而言,这很好,但是一旦你有十几个类,每个类都有十几个属性,它就会开始看起来像很多样板文件。 In particular I feel like I'm defining each property twice, violating DRY.特别是我觉得我定义了每个属性两次,违反了 DRY。 Once on the class and once in __init__ .一次在 class 和一次在__init__

(I'm also slightly concerned about this being a bit "brittle" for situations like the API adding a new option in any given enum, etc. but that's a much lesser concern as I would expect API changes to require changes to my code.) (对于像 API 在任何给定的枚举中添加新选项等情况,我也有点担心这有点“脆弱”,但这是一个不太关心的问题,因为我希望 API 更改需要更改我的代码。 )

I am wondering if there is any magic I can use that will make it so I only have to define each field in one place but still get good typechecking and runtime guarantees that the data is in the form I expect?我想知道我是否可以使用任何魔法来实现它,所以我只需在一个地方定义每个字段,但仍然可以获得良好的类型检查和运行时保证数据是我期望的形式?

I took a look at dataclasses but it seems like I am not able to interfere with the simple string/int inputs of the JSON into enums, datetimes, etc. I could use InitVar to mark a lot of the inputs as " __init__ only" and then use __post_init__ to populate other fields with different names with the strongly-typed version of my data.我查看了数据类,但似乎我无法将dataclasses的简单字符串/整数输入干扰到枚举、日期时间等中。我可以使用InitVar将很多输入标记为“仅限__init__ ”和然后使用__post_init__用我的数据的强类型版本填充具有不同名称的其他字段。 But the "right" name for most of these fields is the name it already has in the JSON (I don't want to have to write event.event_type_typed_version ).但是大多数这些字段的“正确”名称是它在 JSON 中已有的名称(我不想写event.event_type_typed_version )。

Ideally I'd write something like this:理想情况下,我会写这样的东西:

@magic_annotation
class Event:
    start: datetime.datetime
    event_type: EventType

for name, raw_event in data:
    event = Event(**data)
    do_things(name, event)

and not need anything else.并且不需要其他任何东西。 Does magic_annotation exist? magic_annotation存在吗? Is there a completely different way to go about this?关于这个,go 有完全不同的方法吗?

Sounds like you're after pydantic .听起来你在追求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 automatically converts your inputs based on the type annotations of your classes' attributes. Pydantic 根据类属性的类型注释自动转换您的输入。 For datetimes, it supports the standard ISO 8601 formats.对于日期时间,它支持标准的 ISO 8601 格式。 For enums, it converts automatically from the enum values.对于枚举,它会自动从枚举值转换。 Check it out!一探究竟! (Not affiliated, just a fan.) (不隶属,只是一个粉丝。)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM