类型安全的JSON(反)序列化
项目描述
typjson
Python的类型安全JSON(反)序列化。与mypy类型提示兼容。
需求
- Python 3.7或更高版本
特性
- 运行时类型安全以及与mypy兼容
- 支持开箱即用的类型
- 支持自定义编码器和解码器
- API类似于标准json模块
简单用法
from typ import json
from typing import *
from datetime import date
from dataclasses import dataclass
@dataclass
class Address:
street: str
house: int
apt: Optional[str]
@dataclass
class Person:
first_name: str
last_name: str
languages: List[str]
address: Address
birth_date: date
person = Person(
"John",
"Smith",
["English", "Russian"],
Address("Main", 1, "2A"),
date(year=1984, month=8, day=1)
)
json_str = json.dumps(person, indent=2)
loaded_person = json.loads(Person, json_str)
assert person == loaded_person
上面代码示例中,json_str的值看起来像这样被序列化和反序列化
{
"first_name": "John",
"last_name": "Smith",
"languages": [
"English",
"Russian"
],
"address": {
"street": "Main",
"house": 1,
"apt": "2A"
},
"birth_date": "1984-08-01"
}
类型安全
运行时
Python中的类型安全是什么?由于Python是动态类型语言,在运行时之前提供任何类型保证都很难。然而,类型可以在运行时进行检查。这正是typjson库所做的。考虑上面定义的Address类型示例
from typ import json
from typing import *
from dataclasses import dataclass
@dataclass
class Address:
street: str
house: int
apt: Optional[str]
json_str = """{"street": "Main", "house": 1, "apt": 2}"""
loaded_address = json.loads(Address, json_str)
apt字段定义为类型为Optional[str],然而JSON中提供的值是2,它是JSON中的number类型,显然与Optional[str]不兼容。相应地,调用json.loads将引发JsonError
typ.encoding.JsonError: Value 2 can not be deserialized as typing.Union[str, NoneType]
调用json.loads将返回请求类型的实例,并检查所有嵌套类型,或者引发错误。这是typjson的运行时类型安全。
编译时间(mypy)
在 typ.json 模块中,函数 dumps、loads、dump 和 load 都具有适当的类型提示。因此,可以使用 mypy 工具验证类型。
json_str = """{"street": "Main", "house": 1, "apt": 2}"""
loaded_address = json.loads(Address, json_str)
loaded_address = "some other address"
这将在 mypy 中产生错误,因为 loaded_address 的类型被推断为 Address。
error: Incompatible types in assignment (expression has type "str", variable has type "Address")
这提供了编译时的类型安全。
API 概述
typjson API 与 json 模块 API 类似。主要函数定义在 typ.json 模块中。最有用的函数是 typ.json.loads 和 typ.json.dumps。如果在 JSON 编码/解码过程中出现问题,则引发 JsonError。实际上,typ.json 函数在底层使用 json 模块进行 Python 结构与 JSON 之间的最终转换。
支持的类型列表请参阅此处。"自定义编码"部分描述了如何支持任何类型,除了开箱即用的类型。
支持的类型
原始类型
| Python 类型 | JSON 类型 | 注意 |
|---|---|---|
| int | number | |
| float | number | |
| decimal.Decimal | number | |
| boolean | boolean | |
| typ.typing.char | string | 长度为 1 的字符串 |
| str | string | |
| uuid.UUID | string | 带连字符的小写十六进制符号,格式为 8-4-4-4-12 |
| datetime.date | string | ISO 8601 yyyy-mm-dd |
| datetime.datetime | string | ISO 8601 yyyy-mm-ddThh:mm:ss.ffffff |
| datetime.time | string | ISO 8601 hh:mm:ss.ffffff |
| typ.typing.NoneType type(None) |
null |
非原始类型
| Python 类型 | JSON 类型 | 注意 |
|---|---|---|
| List[T] | array | 同构,元素已编码 |
| Dict[str, T] | object | T 的字段值已编码 |
| Set[T] | array | 同构,T 的元素已编码 |
| Tuple[T, K, ...] | array | 异构,T、K、... 的元素已编码 |
| Union[T, K, ...] | 查找 T、K、... | T、K、... 已编码 |
| list | array | 异构,元素已编码 |
| dict | object | |
| tuple | array | 异构,元素已编码 |
| 枚举类 | 查找成员类型 | 枚举成员根据其类型进行编码 |
| 带有装饰器的类 @dataclass |
object | 字段类型受到尊重 |
| 带有装饰器的类 @union |
object | 具有单个字段的对象 |
| Any | 任何类型 | 任何事物 |
空值安全
除了 NoneType 即 type(None) 之外,所有类型都不能有 None 值。 Optional[T] 允许 None 值。因此,如果需要可空 str,则 Optional[str] 将是一个不错的选择。《Optional[T] 类型实际上是 Union[T, NoneType],因此在 typjson 中它通过 Union[] 支持来实现。因此,Optional[T] 没有在上面列出,因为它只是一个 Union。
自定义编码
实际上,所有开箱即用的支持类型都是通过编码器和解码器来支持的。仅提供自定义编码器和解码器的示例以供基本理解。对于更深入的了解,可以查看 typ.encoding 模块的源代码。
自定义编码器
typ.json.dump 和 typ.json.dumps 函数接受编码器列表作为参数。这些编码器是自定义编码器,它们用于除标准内置编码器之外。让我们实现一个自定义编码器,该编码器将所有整数编码为 JSON 中的字符串。
from typ.encoding import Unsupported, check_type
def encode_int_custom(encoder, typ, value):
if typ != int:
# if this encoder is not applicable to the typ it should return Unsupported
return Unsupported
# there's a helper function checking that value is instance of specified type - int
check_type(int, value)
# return encoded value
return str(value)
from typ import json
assert json.dumps([3, 4, 5], encoders=[encode_int_custom]) == '["3", "4", "5"]'
在上面的代码中,encode_int_custom 被传递给 typ.json.dumps 调用,并且在标准内置 int 编码之前使用。正如 assert 所示,它成功地以字符串的形式编码了整数。请勿在现实世界中执行此操作 - 这段代码仅用于演示目的。
编码器函数定义为:Callable[['Encoder', Type[K], K], Union[Any, UnsupportedType]] 每个自定义编码器都有一个 encoder 参数,它持有 Encoder 实例。这对于编码嵌套类型,如列表或类等非常有用。
自定义解码器
typ.json.load 和 typ.json.loads 函数接受解码器列表作为参数。与 编码 类似,它对于自定义解码逻辑非常有用。以下是一个解码 int 类型的 JSON 字符串的镜像示例
from typ.encoding import Unsupported, check_type
def decode_int_custom(decoder, typ, json_value):
if typ != int:
# if this encoder is not applicable to the typ it should return Unsupported
return Unsupported
# check that JSON has string in the json_value
check_type(str, json_value)
# return decoded value
return int(json_value)
from typ import json
assert loads(List[int], '["3", "4", "5"]', decoders=[decode_int_custom]) == [3, 4, 5]
解码器函数定义为:Callable[['Decoder', Type[K], Any], Union[K, UnsupportedType]]。
API 参考文档
typ.json.dumps
typ.json.dumps(value: T, typ: Optional[Type[T]] = None, case: CaseConverter = None, encoders: List[EncodeFunc] = [], indent: Optional[int] = None) -> str
使用指定的类型将值序列化为 JSON 格式的字符串。
value 要序列化为 JSON 的 Python 对象。
typ value 的类型信息。如果提供 None,则使用 value 的实际类型;否则,检查 value 是否是 typ 的有效实例。
case 案例转换器,请参阅 字段案例。
encoders 自定义编码器列表,请参阅 自定义编码。
indent JSON 的可选非负缩进级别。如果提供 None,则 JSON 将表示为单行,没有缩进。
返回 JSON 字符串或抛出 JsonError。
typ.json.dump
typ.json.dump(fp: IO[str], value: T, typ: Optional[Type[T]] = None, case: CaseConverter = None, encoders: List[EncodeFunc] = [], indent: Optional[int] = None) -> None
将值序列化为 JSON 格式的流。
fp 写入 JSON 的流。
其他参数与 typ.json.dumps 中的相同。
typ.json.loads
typ.json.loads(typ: Type[T], json_str: str, case: CaseConverter = None, decoders: List[DecodeFunc] = []) -> T
将 json_str 反序列化为指定类型的 Python 对象。
typ 反序列化 JSON 到的类型。
json_str 包含 JSON 的字符串。
case 案例转换器,请参阅 字段案例。
decoders 自定义解码器列表,请参阅 自定义编码。
返回 M 的实例或抛出 JsonError。
typ.json.load
typ.json.load(fp: IO[str], typ: Type[T], case: CaseConverter = None, decoders: List[DecodeFunc] = []) -> T
将流反序列化为指定类型的 Python 对象。
fp 从中读取 JSON 的流。
其他参数与 typ.json.loads 中的相同。
typ.json.JsonError (定义为 typ.encoding.JsonError)
JsonError 在根据提供的类型信息编码/解码 JSON 数据时引发。
项目详情
下载文件
下载适合您平台的文件。如果您不确定选择哪个,请了解有关 安装包 的更多信息。