跳转到主要内容

一个使反序列化变得简单的库。

项目描述

Code scanning - action PyPI version Azure DevOps builds

反序列化

一个使反序列化变得简单的库。要开始使用,只需运行 pip install deserialize

过去的样子

在没有库的情况下,如果你想将

{
    "a": 1,
    "b": 2
}

转换为专用类,你必须做类似这样的事情

class MyThing:

    def __init__(self, a, b):
        self.a = a
        self.b = b

    @staticmethod
    def from_json(json_data):
        a_value = json_data.get("a")
        b_value = json_data.get("b")

        if a_value is None:
            raise Exception("'a' was None")
        elif b_value is None:
            raise Exception("'b' was None")
        elif type(a_value) != int:
            raise Exception("'a' was not an int")
        elif type(b_value) != int:
            raise Exception("'b' was not an int")

        return MyThing(a_value, b_value)

my_instance = MyThing.from_json(json_data)

现在的样子

使用 反序列化,你所需要做的就是这样做

import deserialize

class MyThing:
    a: int
    b: int

my_instance = deserialize.deserialize(MyThing, json_data)

就是这样。它将提取所有数据并将其设置为你类型检查,甚至检查空值。

如果你想允许空值,那也很简单

from typing import Optional

class MyThing:
    a: Optional[int]
    b: Optional[int]

现在 None 是这些的有效值。

类型可以嵌套到你想要的深度。例如,这是完全有效的

class Actor:
    name: str
    age: int

class Episode:
    title: str
    identifier: st
    actors: List[Actor]

class Season:
    episodes: List[Episode]
    completed: bool

class TVShow:
    seasons: List[Season]
    creator: str

高级用法

自定义键

可能你想要给你的对象属性命名与数据中的不同。这可能是因为可读性原因,或者因为你必须这样做(例如,如果你的数据项被命名为 __class__)。这也同样可以处理。只需使用如下所示的 key 注解

@deserialize.key("identifier", "id")
class MyClass:
    value: int
    identifier: str

现在将具有键 id 的数据分配给字段 identifier。您可以使用多个注释来覆盖多个键。

自动蛇形命名

数据通常会以驼峰式或大驼峰式命名。由于 Python 使用 snake_case 作为成员的标准,这意味着通常会使用自定义键来进行转换。为了使这个过程更简单,您可以添加 auto_snake 装饰器,它会在可能的情况下为您执行此转换。

@deserialize.auto_snake()
class MyClass:
    some_integer: int
    some_string: str

现在您可以传递这些数据,它将自动解析

{
    "SomeInteger": 3,
    "SomeString": "Hello"
}

注意,如果您使用此装饰器,所有字段都需要是蛇形命名。

未处理的字段

通常,如果您在定义中没有指定字段,但它在数据中存在,您只想忽略它。有时,您可能希望知道是否存在额外数据。在这种情况下,当调用 deserialize(...) 时,您可以将 throw_on_unhandled=True 设置为 True,并且如果数据中存在未处理的字段,它将引发异常。

此外,有时您可能需要这样做,但知道某个特定字段可以忽略。您可以使用装饰器 @allow_unhandled("key_name") 将这些标记为允许未处理的。

忽略的键

您可能希望对象中的一些属性不是从磁盘加载,而是以其他方式创建。为此,请使用 ignore 装饰器。以下是一个示例

@deserialize.ignore("identifier")
class MyClass:
    value: int
    identifier: str

在反序列化时,库现在将忽略 identifier 属性。

解析器

有时您希望对象中的某些内容以数据不是的格式存在。例如,如果您获得的数据

{
    "successful": True,
    "timestamp": 1543770752
}

您可能希望它表示为

class Result:
    successful: bool
    timestamp: datetime.datetime

默认情况下,它将因数据中的值不是时间戳而失败。为了纠正这一点,请使用 parser 装饰器来告诉它一个用于解析数据的功能。例如

@deserialize.parser("timestamp", datetime.datetime.fromtimestamp)
class Result:
    successful: bool
    timestamp: datetime.datetime

现在将在处理数据时检测到 key timestamp,并运行它通过提供的解析器函数,然后再将其分配给新的类实例。

解析器是在类型检查之前运行的。这意味着如果您有类似 Optional[datetime.datetime] 的内容,您应该确保您的解析器可以处理值为 None 的情况。您的解析器显然需要返回您在属性中声明的类型才能正常工作。

子类化

支持子类化。例如,如果您有一个类型 Shape,它有一个子类 Rectangle,那么当您尝试将数据解码为 `rectangle` 对象时,对 Shape 上的任何属性都提供支持。

原始存储

有时保留用于构建对象的原始数据引用可能很有用。为此,只需将 raw_storage_mode 参数设置为 RawStorageMode.ROOTRawStorageMode.ALL。这将分别在根对象上或在树中的所有对象上存储数据,参数名为 __deserialize_raw__

默认值

有些数据到达时会缺少字段。在这些情况下,通常已知默认值。为此,只需按如下方式装饰您的类

@deserialize.default("value", 0)
class IntResult:
    successful: bool
    value: int

如果您传入类似 {"successful": True} 的数据,则将 value 的反序列化默认值设置为 0。注意,这不会反序列化,因为 value 不是可选的:{"successful": True, "value": None}

后处理

不是所有内容都可以立即设置在您的数据上。有些事情需要在之后解决。为此,您需要进行一些后处理。最简单的方法是通过 @constructed 装饰器。此装饰器接受一个函数,该函数将在用该实例作为参数构造新实例时调用。以下是一个示例,该示例将极坐标从使用度转换为弧度

data = {
    "angle": 180.0,
    "magnitude": 42.0
}

def convert_to_radians(instance):
    instance.angle = instance.angle * math.pi / 180

@deserialize.constructed(convert_to_radians)
class PolarCoordinate:
    angle: float
    magnitude: float

pc = deserialize.deserialize(PolarCoordinate, data)

print(pc.angle, pc.magnitude)

>>> 3.141592653589793 42.0

向下转型

数据通常会以具有类型字段的形式出现。这可能很难解析。例如

data = [
    {
        "data_type": "foo",
        "foo_prop": "Hello World",
    },
    {
        "data_type": "bar",
        "bar_prop": "Goodbye World",
    }
]

由于两个字段之间不同,没有一种好的方法来解析这些数据。您可以在某些基类上使用可选字段,尝试多次反序列化直到找到正确的方法,或者根据您构建的 data_type 字段映射来执行反序列化。然而,这些方案都不够优雅,如果类型嵌套,所有方案都有问题。相反,您可以使用 downcast_fielddowncast_identifier 装饰器。

downcast_field 在基类上指定,并给出了包含类型信息的字段的名称。downcast_identifier 接受一个基类和一个标识符(这应该是基类中 downcast_field 的可能值之一)。内部,当一个具有下转换字段的类被检测到时,该字段将被提取,并搜索具有匹配标识符的子类。如果不存在此类,将抛出 UndefinedDowncastException

以下是一个处理上述数据的示例

@deserialize.downcast_field("data_type")
class MyBase:
    type_name: str


@deserialize.downcast_identifier(MyBase, "foo")
class Foo(MyBase):
    foo_prop: str


@deserialize.downcast_identifier(MyBase, "bar")
class Bar(MyBase):
    bar_prop: str


result = deserialize.deserialize(List[MyBase], data)

在此,result[0] 将是一个 Foo 的实例,而 result[1] 将是一个 Bar 的实例。

如果您不能描述所有类型,您可以在您的基类上使用 @deserialize.allow_downcast_fallback,并将未知内容保留为字典。

项目详情


下载文件

下载您平台上的文件。如果您不确定选择哪个,请了解更多关于 安装包 的信息。

源分布

deserialize-2.0.1.tar.gz (13.4 kB 查看哈希值)

上传时间

构建分布

deserialize-2.0.1-py3-none-any.whl (15.8 kB 查看哈希值)

上传时间 Python 3

支持者

AWS AWS 云计算和安全赞助商 Datadog Datadog 监控 Fastly Fastly CDN Google Google 下载分析 Microsoft Microsoft PSF赞助商 Pingdom Pingdom 监控 Sentry Sentry 错误日志 StatusPage StatusPage 状态页面