JSON 工具箱

团队协作常见问题:契约、配置与夹具

结论:命名风格、时间格式、空值语义、枚举形态、配置格式、夹具组织与合并冲突,十二条团队里真正会吵起来的取舍。

进阶约 12 分钟发布于 2026-10-30更新于 2026-10-30
团队协作常见问题封面

结论:一次性把这几条写进团队约定——返回体用 {"data":…,"meta":…} 加 HTTP 状态码、命名全站统一一种风格、时间用带时区的 RFC 3339 字符串、枚举用字符串;之后所有新接口都按同一份契约「只加不改不删」地演进。

这些问题没有唯一正确答案,但必须有唯一团队答案——否则每次新接口都要重新吵一遍。

一、接口契约

接口返回的 JSON 该用什么结构?

三种常见形态,各有代价:

形态 例子 优点 缺点
裸数据 [{...}, {...}] 简单 无法扩展元信息
固定包装 {"code":0,"message":"ok","data":{...}} 统一错误处理 业务码与 HTTP 状态重复
资源包装 {"data":{...},"meta":{...},"links":{...}} 可扩展、语义清晰 层级多一层

对内的业务 API 推荐「资源包装 + HTTP 状态码表达结果」:

{
  "data": [{ "id": "1", "name": "示例" }],
  "meta": { "total": 128, "page": 2, "pageSize": 20 }
}

错误时用 HTTP 状态码 + 结构化错误体,不要再套一层 code:

{
  "error": {
    "type": "validation_failed",
    "message": "请求参数校验失败",
    "details": [
      { "path": "/email", "reason": "格式不正确" }
    ]
  }
}

关键点:HTTP 状态码已经表达了成功或失败,业务码是冗余的。两套体系并存时, 「201 但 code 是 500」这种不一致几乎必然出现。

裸数组要避免。顶层是数组时无法附加分页、总数、游标,将来想加就得改所有调用方。

字段命名用 snake_case 还是 camelCase?

选一个,全站统一,不要在同一份 JSON 里混用。

风格 常见生态 例子
camelCase JavaScript / TypeScript / Java userName, createdAt
snake_case Python / Ruby / Go(部分) / 数据库列名 user_name, created_at
kebab-case 罕见,键名里带连字符访问不便 user-name

判断依据:

  1. 谁消费这份 JSON:前端为主选 camelCase,数据管道与 Python 服务为主选 snake_case;
  2. 是否直接映射数据库列:直接透传表结构时用 snake_case 能省一层转换;
  3. 存量系统延续:已有接口是什么就用什么,一致性比正确性重要。

真要转换,放在边界做(网关或前端请求层),不要在业务代码里散落 toCamelCase 调用。

时间字段用什么格式?

用 RFC 3339 / ISO 8601 字符串,带时区偏移:

{
  "createdAt": "2026-10-30T11:37:57+08:00",
  "updatedAt": "2026-10-30T03:37:57Z"
}

要点:

  • 带时区:不带时区的 2026-10-30 11:37:57 在跨时区场景下必然出问题;
  • 统一 Z 或带偏移:两者都对,但一个接口里别混用;
  • 不用时间戳数字:秒还是毫秒、是否含闰秒,每个团队都要重新解释一遍。需要紧凑时用毫秒整数, 但要在文档里写死单位。

字段名建议用 createdAt / updatedAt / deletedAt 这类 At 后缀, 一眼能看出是时间点而不是时长。时长用 timeoutMs、ttlSeconds,单位写进名字。

空值用 null 还是省略字段?

取决于「这个字段有没有意义」:

语义 写法 说明
字段存在但暂无值 "name": null 明确表达「查过了,就是空」
字段与该资源无关 省略 例如普通用户不该有 vipExpireAt
部分更新(PATCH) 省略 = 不改 这时 null 才表示「清空」

第三种最容易被忽略。在 PATCH 请求里:

{ "nickname": null }

意思是「把昵称清空」,而请求体里不出现 nickname 表示「不要动昵称」。 如果两者都当「清空」处理,用户改个邮箱就会把昵称清掉。

再强调一次:不要用 undefined。它不是合法 JSON 值,JSON.stringify 会直接丢掉这个键, 行为与「省略」相同,但代码里看起来像「显式传了空」。

布尔字段怎么命名?

用 is / has / can / should 前缀,避免用否定式。

{
  "isActive": true,
  "hasPassword": false,
  "canEdit": true
}

三条经验:

  1. 不要否定式:isDisabled: false 需要读者做一次取反,isEnabled: true 更直接。 双重否定(isNotDisabled)是灾难;
  2. 不要用数字表示布尔:"enabled": 1 会让每个消费方都写一次 === 1;
  3. 不要用字符串:"enable": "true" 在 JavaScript 里是 truthy, if (data.enable) 对 "false" 也成立,这类 bug 极难排查。

枚举字段用数字还是字符串?

用字符串,除非有明确的存储或带宽压力。

{ "status": "active" }

对比 { "status": 1 },字符串的优势:

  • 日志、抓包、数据库里直接可读,不用查映射表;
  • 新增枚举值不影响已有值的含义(数字枚举中间插入一个值就会错位);
  • 前端不需要同步维护一份 1 → 'active' 的映射。

代价是多几个字节,压缩后几乎可忽略。

配套做法:字符串枚举值用小写 + 下划线(pending_review),并在 Schema 里用 enum 限定, 让非法值在校验阶段就被拦住。

二、配置与夹具

配置用 JSON 还是 YAML / TOML?

按用途选,不要按喜好选:

格式 适合 不适合
JSON 程序读写、需要严格结构、要过 Schema 校验 人工频繁编辑(不能写注释)
YAML 多层嵌套的部署配置(K8s) 手写容易缩进出错;yes/no 会被转成布尔
TOML 扁平的依赖与工具配置(Cargo.toml、pyproject.toml) 深层嵌套可读性差
JSONC 需要注释的编辑器配置(tsconfig.json) 跨系统传输

团队里常见的错误是用 YAML 存业务配置——YAML 的隐式类型转换(1.0 变数字、 NO 变布尔、2026-10-30 变日期)会在不同解析器上产生不同结果。

如果配置要给程序做严格校验,选 JSON + Schema,注释用 _comment 字段或独立文档补充。

测试夹具怎么组织?

按「一份基准 + 最小差异」组织,不要每个用例复制一整份巨大 JSON。

fixtures/
├─ users/
│  ├─ base.json              # 基准用户对象
│  ├─ no-email.json          # 只有 email 缺失
│  └─ invalid-age.json       # 只有 age 越界
└─ fixtures.ts               # 加载与浅合并的工具函数
import baseUser from './users/base.json';

export function makeUser(overrides: Partial<User> = {}): User {
  return { ...baseUser, ...overrides };
}

这样新增用例时只需要写差异字段,改基准字段时所有用例自动跟上。

三条纪律:

  1. 夹具要能被 Schema 校验:基准夹具本身就应该是合法数据;
  2. 不要从生产库导出真实数据:手机号、身份证、地址都是个人信息;
  3. 夹具进版本库,但要小:单文件超过几十 KB 就该考虑裁剪或生成。

JSON 文件合并冲突怎么减少?

JSON 天然容易冲突:格式化工具一次重排整个文件,两个人各改一行就会全文件冲突。

四个手段:

  1. 统一格式化并加进 .editorconfig:缩进、换行、末尾换行一致,避免无关 diff;
  2. 排序键名:约定按字母序排列键,新增字段不会和别人的插入位置打架;
  3. 拆分文件:一个巨型 config.json 拆成按领域分的多个文件,冲突面自然变小;
  4. 避免「一键重排」提交:格式化与业务改动分成两个提交,review 时才看得清。

如果团队用锁文件(pnpm-lock.yaml、package-lock.json),冲突时不要手工合并, 重新生成即可:

git checkout --theirs pnpm-lock.yaml
pnpm install

怎么做版本兼容?

原则是「只加不改不删」。

变更 兼容性 做法
新增可选字段 兼容 直接加,消费方忽略未知字段
新增必填字段 不兼容 升版本,或给默认值
删除字段 不兼容 先标记废弃,观察调用方后再删
改字段类型 不兼容 新增字段名,双写过渡
改枚举值集合 视情况 新增值兼容,删值不兼容

配套机制:

  • 消费方用「宽松解析」:忽略未知字段,字段缺失时给默认值;
  • 服务端保留旧字段一段时间,并在响应里返回 Deprecation 头提示;
  • Schema 里对新增字段不设 required,对废弃字段标 deprecated: true。

怎么在 CI 里校验契约?

把 Schema 当成代码的一部分,提交时校验。

# 校验示例数据是否符合 Schema(用 ajv-cli)
npx ajv-cli validate -s schemas/user.json -d "fixtures/users/*.json" --strict=false

CI 里跑三件事:

  1. Schema 自身的合法性:$schema 声明是否有效、$ref 能否解析;
  2. 夹具是否符合 Schema:保证测试数据不会漂移;
  3. 契约是否向后兼容:对比上一个版本,检查是否删了字段、改了类型。

第三项需要工具支持(如 openapi-diff、json-schema-diff), 没有的话至少用脚本对比 required 数组与 properties 键集合的变化。

三、速查

问题 建议
返回结构 {"data":..., "meta":...},错误用 HTTP 状态码
命名风格 camelCase(前端为主)或 snake_case(数据为主),全站统一
时间格式 RFC 3339 字符串,带时区,字段名以 At 结尾
空值 无值用 null,无关字段省略,PATCH 里省略表示不改
布尔命名 is/has/can 前缀,不用否定式,不用 0/1
枚举 字符串,小写下划线,Schema 里加 enum
配置格式 程序校验用 JSON,人工编辑用 TOML 或 JSONC
夹具 一份基准 + 最小差异覆盖,能被 Schema 校验
合并冲突 统一格式化、排序键名、拆分文件
版本兼容 只加不改不删,废弃字段先标记再删