生成函数以将python类转换为json可转储对象。
json-syntax的Python项目详细描述
json语法
在json兼容结构和本机python之间转换的python库 使用可自定义规则的类。
用例
如果你和作者一样,你试着编写一个编码函数,试图
通过在运行时查询类型进行编码和解码,可以调用
asdict
。这对生成json很有用,但在尝试解码相同的json时,它会变得粗略。
此外,我们在python 3中有注释!即使你不使用类型检查器,只要 标记字段类型使复杂的数据结构更易于理解。
这个库的目标是那些具有复杂json模式的项目 使用库结构,如attrs
- 它通过注释利用了渐进式键入的摘要" rel="nofollow">渐进式键入,输入和数据类
- 它期望使用类型静态地描述类
- 但是可以提供回退来处理运行时描述的数据
- 它提供钩子规范化传统输入
- 用自己的规则扩展库变得很简单
- 操作和规则只是函数
- 可以对编码器和解码器进行pickle
- 库本身没有依赖项
- 它实际上不读或写json
支持的类型
- 原子包括
none
,bool
,int
,float
,str
。- 浮点数可以可选地表示为字符串。
- decimal.decimal类,表示为自身或字符串形式。
datetime.date
和datetime.datetime
类,以ISO8601形式表示。- 初步支持
datetime.timedelta
作为ISO8601时间段。 - 枚举的子类,由字符串名称表示。
- 另外,如果只在 代码
类型。可选的[e]
类型允许用jsonnull
替换值。- 集合包括
typing.list[e]
,typing.tuple[e,…]
,typing.set[e]
和键入.frozenset[e]
。..
是文本,表示一个同质元组,本质上是 冻结列表。
typing.dict[k,v]
类型允许json对象表示同质的dict
。- 限制:键必须是字符串、整数、枚举或日期。
- 使用
attrs.attrs
,dataclasses.dataclass
实现的python类是 表示为json dicts和 - 命名元组通过
typing.named tuple
和异构元组通过typing.tuple
。- 不过,您应该考虑将它们转换为
dataclass
- 不过,您应该考虑将它们转换为
typing.union[a,b,c]
规则将通过检查识别替代类型。
此外,dataclass
和attrs
类支持钩子,让您完全定制
它们的json表示。
用法
这个例子也在单元测试中实现。首先,让我们声明一些类。
importjson_syntaxassynfromdataclassesimportdataclass# attrs works toofromdecimalimportDecimalfromdatetimeimportdatefromenumimportEnum@dataclassclassAccount:user:strtransactions:List['Trans']# Forward references work!balance:Decimal=Decimal()classTransType(Enum):withdraw=0deposit=1@dataclassclassTrans:type:TransTypeamount:Decimalstamp:date
接下来我们将建立一个规则集并使用它来构造一个编码器。标准规则集 函数是一个单行函数,有一些合理的骑。在这里,我们决定是因为 一些中间服务不能可靠地保留十进制值,我们将 用json表示为字符串。
>>>rules=syn.std_ruleset(decimals=syn.decimals_as_str)>>>encode_account=rules.python_to_json(typ=Account)>>>decode_account=rules.json_to_python(typ=Account)
规则集检查类型和动词,搜索其规则列表,然后使用 第一个处理该类型和动词以产生动作的人。
例如,attrs_classes
是一个规则,可以识别动词python_to_json
和
json_to_python
并接受任何用@attr.s
或@dataclass
修饰的类
它将扫描字段并询问规则集如何对它们进行编码。所以当它看到
account.user
,atoms
规则将匹配并报告将astr
转换为json
只需在其上调用str
即可完成。它返回的动作
成为str
内置组件。
因此attrs_classes
将在account
和要转换的操作上构建一个属性列表
它们,并构造一个动作来表示它们。
>>>sample_value=Account(...'bob',[...Trans(TransType.withdraw,Decimal('523.33'),date(2019,4,4))...],Decimal('77.00')...)>>>encode_account(sample_value){'user':'bob','transactions':[{'type':'withdraw','amount':'523.33','stamp':'2019-04-04'}],'balance':'77.00'}
编码和解码
所有这一切的目的都是为了让您的首选json库能够可靠地使用:
withopen('myfile.json','r')asfh:my_account=decode_account(json.load(fh))withopen('myfile.json','w')asfh:json.dump(encode_account(my_account))
使用泛型类型
通常,输入模块simple提供大写字母类型名称,显然 对应于内部类型。有关更详细的介绍,请参见类型
并将内容类型指定为方括号中的参数。
因此我们有:
列表
和列表[e]
设置
和设置[e]
元组
和元组[e,…]
是特例!冻结集
和冻结集[e]
dict
和dict[k,v]
元组是一个特例。在python中,它们通常用于表示"frozenlist",因此
元组[e,…]
(the..
是省略号对象)表示所有元素都具有该类型
e
它们还用于表示未命名的记录。在这种情况下,您可以使用
元组[a,b,c,d]
或任何类型。通常最好使用数据类
标准规则不支持:
- 使用抽象类型,如
iterable
或mapping
- 使用类型变量。
- 任何类型的可调用、协同程序、文件句柄等。 < > >
- <代码>\uu js用
{user':'bob','bal':'77.0',…}
调用on廑u pre廑u decode廑 返回{user':'bob','balance':'77.0',…}
- 解码器针对
user
和balance
和其他字段调用 - 生成的字典被传递到
帐户(**result)
以构造实例。 < > > - 实例的字段被读取并传递给编码器。
- 这些值组合成一个
dict
- 用
{user':'bob','balance':'77.0',…}
和 可以将字段名调整为bal
< > > - 设置:
诗歌安装
- 运行测试:
诗歌运行pytest测试/
- 重新格式化:
诗歌运行黑色json语法/tests/
< > >
活接头
联合类型允许您呈现转换器将尝试使用的替代类型
序列,例如typing.union[mytype,int,myenum]
这在联合规则中实现,称为2
无差别工会。这意味着模块不会向
值,例如某种显式标记。
从python转换为json时,检查通常只使用isinstance, 但是当从json转换为python时,检查可能是检查字符串和dict。 字段.
因此,不明确的值,特别是json表示,可能会混淆解码器。 有关详细信息,请参阅锐边部分。
挂钩
我们将首先检查解码和编码挂钩。这些让我们完全重写json 应用常规逻辑之前的表示。
假设我们的帐户
类用于命名余额
字段余额
,我们需要
支持传统用户。
@dataclassclassAccount:@classmethoddef__json_pre_decode__(cls,value):if'bal'invalue:value=dict(value)value['balance']=value.pop('bal')returnvaluedef__json_post_encode__(self,value):returndict(value,bal=value['balance'])...
解码该值时,将执行以下步骤:
在编码过程中,会发生相反的顺序:
json类型检查挂钩
类型检查只在json语法中使用,以支持类型union;简而言之,
联合
规则将检查某些json,以查看存在哪个变体。
如果未定义类型检查挂钩,则在 标准检查完成。(标准检查试图确定是否需要 字段存在并具有正确的类型。)
如果您掌握的信息可以更快地确定类型,则勾选功能会有所帮助。
回到我们的帐户示例,假设我们决定支持多个帐户类型
通过一个特殊的类
字段。这是更快和更强大的。
classAbstractAccount:@classmethoddef__json_check__(cls,value):returnisinstance(value,dict)andvalue.get('class')==cls.__name__@dataclassclassAccountA(AbstractAccount):...encode_account=rules.lookup(typ=Union[AccountA,AccountB,AccountC],verb='python_to_json')
添加自定义规则
有关自定义规则的详细信息,请参见示例,但通常规则只是 功能。比如说,你的类型有类方法来编码和解码,这个 在许多情况下就足够了:
defmy_rule(verb,typ,ctx):ifissubclass(typ,MyType):ifverb=='json_to_python':returntyp.decoderelifverb=='python_to_json':returntyp.encoder
如果您的规则需要标准类型的编码器或解码器,它可以调用
ctx.lookup(动词=动词,类型=子类型)
。json syntax.action v1中定义的helper函数
旨在保持不变,以便自定义规则可以重用它们。
调试复杂结构
(可能需要更多文档和一些测试用例。)
由于json语法试图将python类型直接转换为json,因此
写出模棱两可的结构。为了避免这种情况,有一个方便的is廑u digulary
方法:
# This is true because both are represented as an array of numbers in JSON.rules.is_ambiguous(typ=Union[List[int],Set[int]])@dataclassclassAccount:user:straddress:str# This is true because such a dictionary would always match the contents of the account.rules.is_ambiguous(typ=Union[Dict[str,str],Account])
这样做的目的是让您检查单元测试,以确保数据可以 根据您的具体情况可靠地表达出来。
在内部,它使用模式
动词来表示json模式,因此
有助于理解json语法如何表示数据:
print(rules.lookup(typ=MyAmbiguousClass,verb='show_pattern'))
锐边
规则集缓存编码器。如果要更改设置,请构造新的规则集。
编码器和解码器很少检查。尤其是在翻译时
从python到json,假设python类是正确的。编码器和
解码器在调用构造函数如str
和int时可能会掩盖一些细微的问题。
为你。而且,根据设计,如果要从json转换为python,则假定
希望能够容忍额外的数据。
所有与打字有关的东西。它有点神奇,有点不是为这个而设计的。 我们有一个指南,可以尝试帮助您
联合类型。您可以使用typing.union
允许成员成为
但也有一些注意事项。您应该使用
规则集警告您这些。
原子规则接受特定类型。目前,原子类型的规则(int
,
str
,bool
,date
,float
,decimal
)必须声明为这些类型。用
多重继承,不清楚应适用规则
检查比转换器更严格。例如,检查int
将检查
该值是一个整数,而转换器只需对其调用int
。因此
输入wheremytype
将通过,但union[mytype,dummy]将失败。(音符
可选的
是要查找的特殊情况,不存在此问题。
数字是硬的。请参阅名为floats
,floats\u nan_str
,decimals
的规则,
decimals_as_str
了解有关如何使数字可靠传输的详细信息。没有规则
分数或复数,因为没有通过json传输它们的规范方法。
维护
这个包是通过诗歌工具来维护的。一些有用的命令:
设置毒性
您需要pyenv,然后安装pythons:
importjson_syntaxassynfromdataclassesimportdataclass# attrs works toofromdecimalimportDecimalfromdatetimeimportdatefromenumimportEnum@dataclassclassAccount:user:strtransactions:List['Trans']# Forward references work!balance:Decimal=Decimal()classTransType(Enum):withdraw=0deposit=1@dataclassclassTrans:type:TransTypeamount:Decimalstamp:date0
一旦在首选的python中安装了tox
,运行它就是tox
(注意:poetry install
现在正在插入tox
,因为pip
已经更改:现在
每次尝试在pip wheel metadata中创建dist。我在查那个目录,但是
很有可能有一些新的配置变量要搜索。)
注释
1:编写编码器似乎很容易,因为 python有完整的信息。标准的json模块提供了一个钩子 您对一个对象进行编码,并使用另一个钩子来识别具有特殊 属性。这可以很好地工作,但是您必须对所有非json类型进行编码。 使用dict包装器使进程反向工作。
2:有区别的联合有一个标识变量的标记,例如
指示成功和有效负载或某个错误的状态代码。严格来说,所有工会
如果执行不同的代码路径,则必须以某种方式进行区分。在联合中
规则,判别式是python中的类信息,以及json的结构。
数据。一个不那么讨人喜欢的描述是,这是一个"差"的歧视
联合。