使用方法
- 打开页面就有一组完整结果:JSON 样例里包含整数、字符串、浮点、布尔、null、字符串数组与嵌套对象七类值,结果区已经给出对应的数据类代码,不需要先填任何东西。
- 「根类名」默认 Root,改成 Order、User 之类符合业务的名字即可;它必须是合法的 Python 标识符,不能数字开头,也不能撞上保留字。
- 「目标 Python 版本」默认 3.7,用 typing 里的 List 与 Optional;选 3.9 会改成内置泛型 list[str],选 3.10 再把 Optional[Any] 写成 Any | None。
- 在 JSON 文本框里按 Ctrl+Enter(Mac 是 Cmd+Enter)生成代码;点「生成代码」或改动上面两项同样触发重算。
- 结果区那一行写明本次的根类名、字段数、嵌套类个数与键名规范化处数;字段数与嵌套类个数能帮你快速判断结构有没有被解析错。
- 「复制代码」复制生成的 Python 源码,「下载 .py」保存成文件——两者都不带字节序标记,直接被 python 命令执行不会报编码错误。
- 下方对照表共 14 行,默认显示 12 行,点「加载更多」看余下 2 行;「导出 CSV」保存为带字节序标记的文件,「复制对照表」复制制表符分隔文本。
- 本页不联网、不写本地存储,刷新即回到默认示例;解析与代码生成都在当前浏览器本地完成。
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。发生改名时,结果区会给出处数,页面上另有「原名 → 采用名」的对照表。
| 变量 | 含义 | 取值 | 影响 |
|---|---|---|---|
| root | 根类名 | 合法 Python 标识符 | 顶层对象生成的类名,默认 Root |
| ver | 目标 Python 版本 | 3.7 / 3.9 / 3.10 | 决定泛型与 Optional 的写法 |
| ann | 字段注解 | str / int / float / bool / List[T] / 类名 | 由值的形态推断,null 记为 Optional |
| def | 字段默认值 | None 或无 | 有默认值的字段被排到末尾 |
| nest | 嵌套类个数 | 非负整数 | 每个嵌套对象生成一个独立类 |
完整示例
默认样例有七个键。id 是 1,不带小数点,注解为 int;name 是字符串,注解为 str;score 是 92.5,带小数点,注解为 float;passed 是布尔,注解为 bool;tags 是两个字符串的数组,元素类型一致,注解为 List[str];profile 是对象,生成独立的 Profile 类并排在 Root 之前;remark 是 null,注解为 Optional[Any] 且带默认 None。
{"id":1,"name":"李雷","score":92.5,"passed":true,"remark":null,
"tags":["vip","new"],"profile":{"city":"北京","age":30}}
from dataclasses import dataclass, field
from typing import Any, Dict, List, Optional
@dataclass
class Profile:
city: str
age: int
@dataclass
class Root:
id: int
name: str
score: float
passed: bool
tags: List[str]
profile: Profile
remark: 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 行。
| JSON 形态 | Python 注解 | 备注 |
|---|---|---|
| 字符串 | str | 直接映射,不做长度或格式校验 |
| 整数 number | int | 不含小数点与指数 |
| 浮点 number | float | 含小数点或科学计数法 |
| true / false | bool | JSON 的布尔字面量 |
| null | Optional[Any] = None | 带默认值,排到字段末尾 |
| 同类型数组 | List[T] | 取元素类型 |
| 混合类型数组 | List[Any] | 类型不一致时回退 |
| 空数组 | List[Any] | 无法推断元素类型 |
| 对象 | 嵌套 dataclass | 类名由字段名转 PascalCase |
| 对象数组 | List[子类] | 子类先定义再被引用 |
| 键名撞保留字 | 原名 + 下划线 | 共 35 个保留字 |
| 键名含连字符 | 转 snake_case | 连字符不是合法标识符字符 |
| 顶层是数组 | 取第一个元素推断 | 本页不生成 List[Root] 包装 |
| 可变默认值 | field(default_factory=list) | 直接写 [] 会抛 ValueError |
第二张表是键名规范化的「原名 → 采用名」对照。改名只发生在键名本身不是合法标识符、或者撞上保留字的时候,其余键名原样保留。
| 原名 | 采用名 | 原因 |
|---|---|---|
| class | class_ | 撞上 Python 保留字 |
| from | from_ | 撞上 Python 保留字 |
| user-name | user_name | 连字符不是合法标识符字符 |
| 2fa | _2fa | 标识符不能以数字开头 |
结果边界与错误提示
- JSON 解析失败时给出行号与列号:提示写明「第几行第几列(第几个字符)附近解析失败」,并附上引擎的原始描述,不会只说「JSON 格式错误」。
- 文本框被清空时点「生成代码」会自动填回默认样例;只有空白字符时同样按空处理。
- 顶层既不是对象也不是数组(例如裸写数字、字符串或 null)时,提示写明顶层的实际类型,并说明无法映射为单个 dataclass。
- 顶层是数组时,本页取第一个元素推断并显式说明这一点;空数组则提示无法推断元素结构,不会生成一个空类。
- 「根类名」不是合法标识符(空、数字开头、含非法字符、撞上保留字)时,提示点名具体是哪个问题,不小修后继续生成。
- 字段数非常多、嵌套很深时,输出会很长但结构不变;本页不对嵌套层数设限,也不截断输出。
- 下载的 .py 与「复制代码」都不带字节序标记,避免 python 命令把 BOM 当成语法错误;只有对照表的 CSV 导出带 BOM。
- 本页不上传任何输入,也不写本地存储;样例里的真实姓名、手机号等敏感信息不会离开浏览器。
常见问题
为什么 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 开门用的。
相关工具
- JSON转XML转换器:把 JSON 对象转成标签树。
- CSV转JSON转换器:把表格导出转成对象数组。
- JSON转CSV转换器:反向把对象数组摊成表格。
- SQLInsert转JSON转换器:解析 INSERT 语句生成 JSON。
- JSON格式化工具:整理缩进与压缩单行输出。