类型安全的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 数据时引发。
项目详情
下载文件
下载适合您平台的文件。如果您不确定选择哪个,请了解有关 安装包 的更多信息。