Python 如何高效地将 JSON 反序列化为对象

2021-03-22 11:17:24 +08:00
 mimzy

现在通过 API 获取的 JSON,我一般先用 HTTPX.json() 方法转换为 Python 内置数据结构,然后用 Pydanticparse_obj_as() 转化为对象(因为 parse_obj_as() 可以很方便地转换 list ),便于使用 type hints 。

目前的链路是这样的:str -> dict/list -> Pydantic object,有点冗余,而且当 JSON 体积大到一定程度的时候,第二步比第一步慢一个数量级,已经无法接受。所以想了解下,有没有其他高效地将 JSON 反序列化为对象的方法?

4253 次点击
所在节点    Python
28 条回复
ungrown
2021-03-22 11:37:23 +08:00
可以借助 JSONPath 之类的东西做个封装对象
ch2
2021-03-22 11:41:31 +08:00
自己写一个 converter
hahastudio
2021-03-22 11:47:49 +08:00
knightdf
2021-03-22 11:48:35 +08:00
ujson,比内置 json 快
so1n
2021-03-22 11:56:58 +08:00
第一步可以用 ujson ojson 代替 第二步目前还是 pydantic 最快
mimzy
2021-03-22 12:05:04 +08:00
@hahastudio #3 昨天看了 orjson,文档中 https://github.com/ijl/orjson#deserialize 提到 loads() deserializes JSON to Python objects. It deserializes to dict, list, int, float, str, bool, and None objects. 我希望能像 Pydantic 一样返回一个对象而不是内置数据结构,就感觉不太符合…
abersheeran
2021-03-22 12:18:35 +08:00
有一个问题,你需要进行默认值的填充和参数校验吗?如果你不需要这些。

写一个类似于这里面的 https://github.com/abersheeran/index.py/blob/master/indexpy/utils.py#L50 类就行了。基本思路是使用三个魔术方法来自定义 obj.attr 的行为。比起其他需要校验、填充默认值的玩意,快不止一个数量级,因为这里压根就没有 COPY 的损耗。
no1xsyzy
2021-03-22 12:34:05 +08:00
那 type hint 就是继承之后再写咯……
Kobayashi
2021-03-22 12:35:29 +08:00
第二步要做类型校验、转换,这能一样吗?
berserk
2021-03-22 12:36:46 +08:00
import json
json.loads(s)不行吗
qlhai
2021-03-22 12:51:02 +08:00
你都已经用 Python 了,还在乎这点时间吗
Vegetable
2021-03-22 12:55:08 +08:00
parse_obj_as 是要验证的,如果数据是可信的,可以跳过验证,根据官方文档的说法:

construct() is generally around 30x faster than creating a model with full validation
mimzy
2021-03-22 12:57:01 +08:00
@abersheeran #7 参数校验其实不需要,这个场景下我获取数据,格式基本可以保证。问题在于它传给我的结构嵌套可能比较多,所以希望用自定义对象的方式访问,这样每一层我能知道对象拥有的属性和类型,而不是从字典里一层一层访用 key 访问…你这个方式我学习一下,不过看起来没办法让 IDE 给我提示…
mimzy
2021-03-22 13:03:09 +08:00
@Vegetable #12 是的,昨天也简单看了下 construct(),但是看文档 https://pydantic-docs.helpmanual.io/usage/models/#creating-models-without-validation 它接收的是一堆参数,这就导致我要将返回的 List[Dict[str, Any]] 这样的东西循环一次,然后将 **dict 作为参数,这么构造完已经 30s 了,当然可能我用得不对…

如果 construct() 和 parse_raw() 能结合的话,我估计效率应该会提升。
mimzy
2021-03-22 13:07:03 +08:00
@berserk #10 .json() 这一步其实就做完了这件事,我想要的是一个我能确定内部结构的对象,类型于 Go 的 structs 。不过这么说的话,突然想起来好像用 TypedDict 注解一下也行…
Contextualist
2021-03-22 13:08:35 +08:00
不需要参数校验的话,可以试试 attrs + cattrs 。我自己在用这个方案,但是是用来反序列化配置文件的,所以没有考虑性能。另外 pydantic 我没用过,没有发言权。据 pydantic 作者自己说估计 attrs 能更快: https://github.com/samuelcolvin/pydantic/issues/1459#issuecomment-622045131
mimzy
2021-03-22 13:26:33 +08:00
@mimzy #15 目前的结论:在不需要参数校验的前提下,因为我只是需要掌握对象的内部结构和类型,TypedDict 基本解决了我的需求…
abersheeran
2021-03-22 13:37:58 +08:00
@mimzy 如果你只是需要代码提示,TypedDict 永远的神……https://github.com/abersheeran/baize/blob/master/baize/typing.py#L66
mimzy
2021-03-22 14:08:44 +08:00
@abersheeran #18 我之前就是觉得 TypedDict 只用来做类型注解,然后要写一堆,太浪费了,所以上了 Pydantic 。不过看来有时候返璞归真也挺好…
abersheeran
2021-03-22 14:40:24 +08:00
@mimzy 另外一提,如果你没有用 pydantic 提供的 Cython 编译后的版本,其实它的速度和 attrs 之流差不多甚至慢一些。有时候觉得 pydantic 、fastapi 这些家伙的宣传挺可耻的……虚假宣传

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/763871

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX