JSON转Python dataclass_数据类代码生成

把一份 JSON 样例解析成带类型注解的 Python 数据类:字符串映射为 str,整数与浮点分别映射为 int 与 float,数组取元素类型的并集,嵌套对象生成独立的类并排在引用它的类之前。null 字段注解为 Optional[Any] 并带默认 None,因此会被移到字段列表末尾。可选 3.7、3.9、3.10 三档写法,可下载不带 BOM 的 .py 文件。

使用方法

  1. 打开页面就有一组完整结果:JSON 样例里包含整数、字符串、浮点、布尔、null、字符串数组与嵌套对象七类值,结果区已经给出对应的数据类代码,不需要先填任何东西。
  2. 「根类名」默认 Root,改成 Order、User 之类符合业务的名字即可;它必须是合法的 Python 标识符,不能数字开头,也不能撞上保留字。
  3. 「目标 Python 版本」默认 3.7,用 typing 里的 List 与 Optional;选 3.9 会改成内置泛型 list[str],选 3.10 再把 Optional[Any] 写成 Any | None。
  4. 在 JSON 文本框里按 Ctrl+Enter(Mac 是 Cmd+Enter)生成代码;点「生成代码」或改动上面两项同样触发重算。
  5. 结果区那一行写明本次的根类名、字段数、嵌套类个数与键名规范化处数;字段数与嵌套类个数能帮你快速判断结构有没有被解析错。
  6. 「复制代码」复制生成的 Python 源码,「下载 .py」保存成文件——两者都不带字节序标记,直接被 python 命令执行不会报编码错误。
  7. 下方对照表共 14 行,默认显示 12 行,点「加载更多」看余下 2 行;「导出 CSV」保存为带字节序标记的文件,「复制对照表」复制制表符分隔文本。
  8. 本页不联网、不写本地存储,刷新即回到默认示例;解析与代码生成都在当前浏览器本地完成。

JSON转Python数据类能解决什么

接口文档给了一份 JSON 样例,接下来要写请求体校验、写 ORM 模型、写配置类,最枯燥的一步是把字段一个一个抄成类属性,还要逐个判断类型。抄错一个下划线、漏掉一个可选字段,运行时才会炸。本页做的是把这一步自动化:解析样例、推断注解、生成可以直接粘进项目的 @dataclass 代码。

第一类场景是接口对接。拿到第三方返回的 JSON,先跑一遍本页,得到的结构定义可以直接作为解析层的基础,再按业务补上校验与默认值。嵌套对象会自动拆成独立的类,不会出现一个几十字段的扁平大类。

第二类场景是配置与夹具。配置文件、测试数据、爬虫的中间结果,都可以先写成 JSON 再转成数据类,好处是字段有类型注解,IDE 能补全、mypy 能检查,比直接吃字典可靠得多。

边界也要说清:本页只按「一份样例」推断,看不到字段的可选性语义。样例里是 null 的字段会被当成 Optional 并给默认 None,样例里出现的值则按具体类型注解;如果样例没有覆盖所有分支,生成的代码就只是这份样例的结构,需要你按接口文档补充。

类型推断与代码生成原理

dataclasses 来自 PEP 557,自 Python 3.7 起进入标准库。@dataclass 装饰器会按类里的注解自动生成 __init__、__repr__ 与 __eq__,因此一个只有字段与注解的类就等价于手写的样板代码,这也是它适合承载 JSON 结构的原因。

类型映射是核心:string 映射为 str;number 里不带小数点与指数的是 int,带的是 float;true 与 false 映射为 bool;null 映射为 Optional[Any] 并给默认 None;array 映射为 List[T],T 由元素推断;object 生成一个嵌套的独立 dataclass,类名由字段名转成 PascalCase。

数组的元素类型取并集:所有元素同类型就取该类型,混合类型回退到 List[Any],空数组同样按 List[Any] 处理。元素本身是对象时,先生成这个元素的类,再把数组注解成 List[子类]。

嵌套类必须先定义。Python 在解析类体时会立刻求值注解,如果引用了一个还没定义的类名,就会抛 NameError。因此本页按「先递归子对象、再登记父对象」的顺序输出:子类的定义一定排在引用它的类之前。

键名规范化的规则有三条:非字母数字下划线的字符换成下划线;数字开头补一个下划线;撞上 Python 保留字的加一个下划线后缀。保留字共 35 个:False、None、True、and、as、assert、async、await、break、class、continue、def、del、elif、else、except、finally、for、from、global、if、import、in、is、lambda、nonlocal、not、or、pass、raise、return、try、while、with、yield。发生改名时,结果区会给出处数,页面上另有「原名 → 采用名」的对照表。

完整示例

默认样例有七个键。id 是 1,不带小数点,注解为 int;name 是字符串,注解为 str;score 是 92.5,带小数点,注解为 float;passed 是布尔,注解为 bool;tags 是两个字符串的数组,元素类型一致,注解为 List[str];profile 是对象,生成独立的 Profile 类并排在 Root 之前;remark 是 null,注解为 Optional[Any] 且带默认 None。

注意 remark 的位置:它在 JSON 里排在 passed 之后、tags 之前,但生成的代码里被移到了最后。这不是随意调整顺序——带默认值的字段一旦出现在无默认值字段之前,Python 会抛 TypeError: non-default argument follows default argument,这个类根本没法定义。把「目标 Python 版本」改成 3.10,最后一行会变成 remark: Any | None = None,其余不变。

类型映射对照表

下表列出十四条映射与规范化规则,均为本页生成器的实际行为。共 14 行,默认显示 12 行,点工具区的「加载更多」看余下 2 行。

第二张表是键名规范化的「原名 → 采用名」对照。改名只发生在键名本身不是合法标识符、或者撞上保留字的时候,其余键名原样保留。

结果边界与错误提示

常见问题

为什么 remark 被移到了最后?

因为它是 null,被注解为 Optional[Any] 并带默认 None。Python 规定带默认值的参数必须排在无默认值参数之后,否则抛 TypeError: non-default argument follows default argument。移到末尾是让这段代码能被定义,不是随意调整顺序。

为什么嵌套类要排在前面?

因为 Python 解析类体时会立刻求值注解。Root 里写了 profile: Profile,而 Profile 还没定义,就会抛 NameError。本页按先递归子对象、再登记父对象的顺序输出,保证被引用的类一定已经定义过。

数组元素类型怎么定?

取所有元素类型的并集:全都一样就取该类型,只要出现不一致就回退到 List[Any],空数组同样按 List[Any] 处理。元素本身是对象时,会先生成这个元素的类,再把数组注解成 List[子类]。

3.7、3.9、3.10 三档差在哪?

3.7 用 typing 里的 List[T] 与 Optional[X];3.9 起可以写内置泛型 list[str];3.10 起还能写 X | None,于是 Optional[Any] 变成 Any | None。选哪一档取决于你项目实际运行的最低 Python 版本。

键名撞上保留字会怎样?

加一个下划线后缀,例如 class 变成 class_、from 变成 from_。Python 的保留字共 35 个,页面内置了完整清单;发生改名时结果区会给出处数,你可以对照「原名 → 采用名」那张表核对。

可变默认值为什么不能直接写?

因为 dataclass 会把默认值挂在类上,所有实例共享同一个列表,改动一个会污染其它实例。Python 直接禁止这种写法并抛 ValueError: mutable default ... use default_factory,正确写法是 field(default_factory=list)。

顶层是数组怎么办?

本页取第一个元素来推断结构,并在结果里显式说明这一点;若要的是一整列表,可以自行把生成的类名包进 List[...],或者改用一个元素的对象作为样例。

下载的 .py 带字节序标记吗?

不带。.py 与「复制代码」都是纯 UTF-8 无 BOM 文本,python 命令直接执行不会报编码错误;只有对照表的 CSV 导出带字节序标记,那是给 Excel 开门用的。

相关工具