JSON 转代码,是把 JSON 样本或正式契约转换为接口、结构体、类、序列化器或验证器等源代码声明。它可以减少样板工作,但单个样本只是一个实例的证据,并不能证明完整数据模型。生成的类型可以改善编辑器提示,却不能让运行时输入自动合法。
核心要点
- 优先以 JSON Schema 或 OpenAPI 作为事实来源。只有在明确记录不确定性的情况下,才从代表性样本生成草稿。
- 保留可选性、
null、数字精度、异构数组、未知字段和格式约束,不要从一个“成功样本”臆测。 - 把属性名、描述和枚举值视为不可信输入。清洗标识符,并转义注释、字符串和模板片段。
- 将生成文件与手写业务逻辑分离,让重新生成可复现。
- 编译期类型不校验网络数据。应配合运行时解码、Schema 验证或会检查响应的类型化客户端。
- 在 CI 中固定生成器和模板版本,执行格式化、编译、测试和生成 diff 审查。
样本、Schema 与契约
这些输入的权威程度不同:
| 输入 | 可以说明 | 不能证明 |
|---|---|---|
| 一个 JSON 样本 | 观察到的值和形状 | 可选性、所有变体、限制、未来字段 |
| 多个样本 | 更多观察到的变体 | 未出现的变体不可能存在 |
| JSON Schema | 断言、注解和引用 | 业务授权或副作用 |
| OpenAPI 文档 | HTTP 操作、Schema、参数和响应 | 未经测试的真实服务行为 |
| 数据库/IDL 契约 | 源领域字段和类型 | 未结合样本的线路转换细节 |
如果生成代码代表 API,应分别定义请求和响应 Schema。响应模型不应自动复用为输入模型:可写字段、默认值、服务端 ID 和密钥字段通常不同。
类型映射是一项策略
| JSON 值 | 可能的目标类型 | 必须记录的决策 |
|---|---|---|
| string | string、String、str |
日期、URI、UUID、十进制或普通文本 |
| number | 整数、十进制、number、big.Int |
精度、范围和线路表示 |
| boolean | boolean、bool |
通常直接映射 |
null |
nullable/可选联合 | null 是否不同于缺失 |
| array | 列表、元组、联合列表 | 同质、元组或异构数组 |
| object | 类、结构体、Map、record | 封闭、开放或扩展字段 |
JavaScript number 和许多生成语言的基础数值类型无法精确表示所有 JSON 整数。金额和标识符往往应使用字符串或十进制类型。一个样本中是整数的字段,后续可能出现小数或字符串;契约必须决定这是非法、联合类型,还是迁移过程。
处理不确定性
可选字段与 null
只有当契约允许缺失时,字段在样本中缺失才能生成可选类型。如果字段出现 null,应单独表达可空性。TypeScript 的 field?: string 与 field: string | null 是不同状态,不能合并。
数组
只有检查代表性元素后,才能推断同质列表。数组可能包含由 kind 区分的联合变体。应为变体生成命名类型并校验 discriminator,不要静默使用 any 或 object。
未知属性
选择生成模型对未知属性的处理:拒绝、保留或忽略。前向兼容客户端可能保留扩展数据,安全敏感输入可能拒绝未知字段以避免批量赋值。生成器无法仅凭 JSON 选择这项策略。
名称与冲突
将 snake_case、连字符、保留字、空名称和数字开头的键映射为合法标识符,同时保留原始线路名称:
export interface UserRecord {
userId: string; // wire key: "user_id"
["class"]: string; // 保留保留字线路键
}
使用 Go 的 json:"user_id" 或 Java 注解等显式序列化标签。绝不能允许输入键名或描述注入源代码、注释、导入语句或模板指令。
契约驱动示例
先从 Schema 开始,而不是猜测接口:
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://example.com/schemas/user",
"type": "object",
"required": ["id", "name"],
"properties": {
"id": { "type": "string", "minLength": 1 },
"name": { "type": "string", "minLength": 1 },
"email": { "type": ["string", "null"], "format": "email" },
"roles": {
"type": "array",
"items": { "type": "string" },
"uniqueItems": true
}
},
"additionalProperties": false
}
生成的 TypeScript 声明可以保留契约:
export interface User {
id: string;
name: string;
email: string | null;
roles: string[];
}
声明可以帮助编译器,但不会校验 fetch() 的输出。应在边界校验响应,只有缩窄后的值才能进入业务逻辑。
多语言输出
生成代码应使用符合语言习惯的线路标签,并明确可空性:
type User struct {
ID string `json:"id"`
Name string `json:"name"`
Email *string `json:"email"`
Roles []string `json:"roles"`
}
from dataclasses import dataclass
from typing import Optional
@dataclass
class User:
id: str
name: str
email: Optional[str]
roles: list[str]
这些声明仍需要 JSON 解码器和校验策略。Java 应区分可空引用和基本类型;C# 应明确可空引用注解和序列化器选项。不要把省略导入、getter 或依赖版本的片段描述为完整构建。
边界上的运行时校验
安全流程是:
- 使用严格 JSON 解析器解析字节,并限制大小和深度;
- 根据版本化 Schema 校验解析后的值;
- 授权已认证主体访问目标对象和执行操作;
- 把通过校验的值映射到生成或手写领域类型;
- 单独执行业务不变量和副作用策略。
编译期类型无法阻止恶意或过期服务返回意外值。运行时校验也不会自动授予授权,或证明字段属于当前租户。
生成器与仓库工作流
固定生成器、模板、目标语言版本和 Schema 修订。把生成结果放在明确标记的目录中,把自定义方法放在独立文件或扩展点。可复现流水线应:
- 对无效 Schema 和含糊样本失败;
- 生成包含源哈希、生成器版本、参数和输出哈希的清单;
- 格式化并编译生成代码;
- 为可选、null、联合、大数字、Unicode、未知字段和错误输入运行 fixture 测试;
- 审查生成 diff,避免覆盖手写文件。
不要把生产密钥、访问令牌或个人数据放进在线生成器或提交到仓库的 fixture。生成器需要加载引用时,使用固定的本地注册表或允许列表。
常见失败模式
| 失败 | 原因 | 控制措施 |
|---|---|---|
| 必填字段被生成成非空 | 生成器只看到完整样本 | 从 Schema 和多个 fixture 生成 |
| 大 ID 丢失数字 | 目标运行时数值精度不足 | 使用字符串或精确十进制/整数 |
| 服务新增字段导致客户端失败 | 模型封闭或反序列化严格 | 定义未知字段和兼容性策略 |
| 线路键不再匹配 | 名称规范化丢失原始键名 | 输出显式序列化标签 |
| 生成代码执行输入内容 | 键/注释/模板注入 | 清洗并转义所有源片段 |
| 无效响应进入业务逻辑 | 把编译期类型误当运行时校验 | 在边界解码并校验 |
常见问题
一个 JSON 样本能生成完整模型吗?
不能。它可以生成有用的起点,但无法证明可选字段、所有变体、限制、格式或未来兼容性。应把结果当草稿,并与 Schema 或多个 fixture 对照审查。
生成代码就是类型安全吗?
它可以改善编译期检查,但只在模型假设范围内有效。网络数据在运行时解码和校验成功前仍是不可信输入。
生成模型应同时用于请求和响应吗?
通常不应未经审查复用。服务端 ID、只读字段、默认值、只写密钥和响应元数据往往需要独立的输入/输出模型。
应如何处理未知 JSON 字段?
必须明确选择。拒绝可以捕获拼写错误和批量赋值风险;保留或忽略可以改善前向兼容。策略取决于信任边界、客户端角色和演进方案。
在线 JSON 转代码生成器适合生产 payload 吗?
不要默认适合。应核实上传、留存、遥测、引用抓取和删除行为。凭据、个人数据和专有 Schema 应使用本地或获批准的受控处理。
一手来源
- JSON Schema 2020-12
- OpenAPI Specification
- RFC 8259:JSON 数据交换语法
- Go
encoding/json文档 - OWASP:Input Validation Cheat Sheet
- OWASP:Code Injection
总结
JSON 转代码最可靠的方式,是从版本化契约开始,让不确定性可见,保留线路键和可空性,并在业务逻辑前校验运行时数据。把生成文件当可复现产物,审查其 diff,把授权和领域不变量留在类型声明之外。