JSON 转代码,是把 JSON 样本或正式契约转换为接口、结构体、类、序列化器或验证器等源代码声明。它可以减少样板工作,但单个样本只是一个实例的证据,并不能证明完整数据模型。生成的类型可以改善编辑器提示,却不能让运行时输入自动合法。

核心要点

  • 优先以 JSON Schema 或 OpenAPI 作为事实来源。只有在明确记录不确定性的情况下,才从代表性样本生成草稿。
  • 保留可选性、null、数字精度、异构数组、未知字段和格式约束,不要从一个“成功样本”臆测。
  • 把属性名、描述和枚举值视为不可信输入。清洗标识符,并转义注释、字符串和模板片段。
  • 将生成文件与手写业务逻辑分离,让重新生成可复现。
  • 编译期类型不校验网络数据。应配合运行时解码、Schema 验证或会检查响应的类型化客户端。
  • 在 CI 中固定生成器和模板版本,执行格式化、编译、测试和生成 diff 审查。

样本、Schema 与契约

这些输入的权威程度不同:

输入 可以说明 不能证明
一个 JSON 样本 观察到的值和形状 可选性、所有变体、限制、未来字段
多个样本 更多观察到的变体 未出现的变体不可能存在
JSON Schema 断言、注解和引用 业务授权或副作用
OpenAPI 文档 HTTP 操作、Schema、参数和响应 未经测试的真实服务行为
数据库/IDL 契约 源领域字段和类型 未结合样本的线路转换细节

如果生成代码代表 API,应分别定义请求和响应 Schema。响应模型不应自动复用为输入模型:可写字段、默认值、服务端 ID 和密钥字段通常不同。

类型映射是一项策略

JSON 值 可能的目标类型 必须记录的决策
string stringStringstr 日期、URI、UUID、十进制或普通文本
number 整数、十进制、numberbig.Int 精度、范围和线路表示
boolean booleanbool 通常直接映射
null nullable/可选联合 null 是否不同于缺失
array 列表、元组、联合列表 同质、元组或异构数组
object 类、结构体、Map、record 封闭、开放或扩展字段

JavaScript number 和许多生成语言的基础数值类型无法精确表示所有 JSON 整数。金额和标识符往往应使用字符串或十进制类型。一个样本中是整数的字段,后续可能出现小数或字符串;契约必须决定这是非法、联合类型,还是迁移过程。

处理不确定性

可选字段与 null

只有当契约允许缺失时,字段在样本中缺失才能生成可选类型。如果字段出现 null,应单独表达可空性。TypeScript 的 field?: stringfield: string | null 是不同状态,不能合并。

数组

只有检查代表性元素后,才能推断同质列表。数组可能包含由 kind 区分的联合变体。应为变体生成命名类型并校验 discriminator,不要静默使用 anyobject

未知属性

选择生成模型对未知属性的处理:拒绝、保留或忽略。前向兼容客户端可能保留扩展数据,安全敏感输入可能拒绝未知字段以避免批量赋值。生成器无法仅凭 JSON 选择这项策略。

名称与冲突

snake_case、连字符、保留字、空名称和数字开头的键映射为合法标识符,同时保留原始线路名称:

typescript
export interface UserRecord {
  userId: string; // wire key: "user_id"
  ["class"]: string; // 保留保留字线路键
}

使用 Go 的 json:"user_id" 或 Java 注解等显式序列化标签。绝不能允许输入键名或描述注入源代码、注释、导入语句或模板指令。

契约驱动示例

先从 Schema 开始,而不是猜测接口:

json
{
  "$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 声明可以保留契约:

typescript
export interface User {
  id: string;
  name: string;
  email: string | null;
  roles: string[];
}

声明可以帮助编译器,但不会校验 fetch() 的输出。应在边界校验响应,只有缩窄后的值才能进入业务逻辑。

多语言输出

生成代码应使用符合语言习惯的线路标签,并明确可空性:

go
type User struct {
	ID    string   `json:"id"`
	Name  string   `json:"name"`
	Email *string  `json:"email"`
	Roles []string `json:"roles"`
}
python
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 或依赖版本的片段描述为完整构建。

边界上的运行时校验

安全流程是:

  1. 使用严格 JSON 解析器解析字节,并限制大小和深度;
  2. 根据版本化 Schema 校验解析后的值;
  3. 授权已认证主体访问目标对象和执行操作;
  4. 把通过校验的值映射到生成或手写领域类型;
  5. 单独执行业务不变量和副作用策略。

编译期类型无法阻止恶意或过期服务返回意外值。运行时校验也不会自动授予授权,或证明字段属于当前租户。

生成器与仓库工作流

固定生成器、模板、目标语言版本和 Schema 修订。把生成结果放在明确标记的目录中,把自定义方法放在独立文件或扩展点。可复现流水线应:

  • 对无效 Schema 和含糊样本失败;
  • 生成包含源哈希、生成器版本、参数和输出哈希的清单;
  • 格式化并编译生成代码;
  • 为可选、null、联合、大数字、Unicode、未知字段和错误输入运行 fixture 测试;
  • 审查生成 diff,避免覆盖手写文件。

不要把生产密钥、访问令牌或个人数据放进在线生成器或提交到仓库的 fixture。生成器需要加载引用时,使用固定的本地注册表或允许列表。

常见失败模式

失败 原因 控制措施
必填字段被生成成非空 生成器只看到完整样本 从 Schema 和多个 fixture 生成
大 ID 丢失数字 目标运行时数值精度不足 使用字符串或精确十进制/整数
服务新增字段导致客户端失败 模型封闭或反序列化严格 定义未知字段和兼容性策略
线路键不再匹配 名称规范化丢失原始键名 输出显式序列化标签
生成代码执行输入内容 键/注释/模板注入 清洗并转义所有源片段
无效响应进入业务逻辑 把编译期类型误当运行时校验 在边界解码并校验

常见问题

一个 JSON 样本能生成完整模型吗?

不能。它可以生成有用的起点,但无法证明可选字段、所有变体、限制、格式或未来兼容性。应把结果当草稿,并与 Schema 或多个 fixture 对照审查。

生成代码就是类型安全吗?

它可以改善编译期检查,但只在模型假设范围内有效。网络数据在运行时解码和校验成功前仍是不可信输入。

生成模型应同时用于请求和响应吗?

通常不应未经审查复用。服务端 ID、只读字段、默认值、只写密钥和响应元数据往往需要独立的输入/输出模型。

应如何处理未知 JSON 字段?

必须明确选择。拒绝可以捕获拼写错误和批量赋值风险;保留或忽略可以改善前向兼容。策略取决于信任边界、客户端角色和演进方案。

在线 JSON 转代码生成器适合生产 payload 吗?

不要默认适合。应核实上传、留存、遥测、引用抓取和删除行为。凭据、个人数据和专有 Schema 应使用本地或获批准的受控处理。

一手来源

总结

JSON 转代码最可靠的方式,是从版本化契约开始,让不确定性可见,保留线路键和可空性,并在业务逻辑前校验运行时数据。把生成文件当可复现产物,审查其 diff,把授权和领域不变量留在类型声明之外。