很多项目里,JSON 只是个“数据容器”:字段往里一塞,接口就算通了。但真正经得起时间、团队、业务变化考验的 JSON,需要把“表示、约束、演进、效率、安全”一起考虑。下面这篇从实践出发,讲清楚设计 JSON 时该想什么、怎么取舍、如何落地。
一、先回答三个基本问题
1)这份 JSON 的边界是什么?
它是一个领域对象(如 User)、一个聚合视图(统计报表)、还是传输协议负载(API 的 request/response)?边界决定了字段粒度、是否需要冗余、以及能否被复用。
2)读写路径是谁?
是 JS 前端直读?还是 Python 批处理?不同语言对数值精度、日期解析、大小写习惯都不一样,设计时要预想“消费者”的真实能力与坑点。
3)生命周期怎么演进?
这份 JSON 以后会扩展吗?向后兼容是否必须?版本如何管理?如果一开始就接受“会变化”,你就会主动为未来留扩展位与约束机制。
二、命名与结构:可读、稳定、可演进
命名风格统一
-
前后端一体化项目常用 camelCase;数据仓库/ETL 往往偏好 snake_case。关键在“统一”,混用最难维护。
-
避免缩写(usr_id 不如 user_id),避免语义含糊(value、data)。
-
字段名建议稳定,新增胜过重命名(重命名会破坏兼容性)。
对象 vs 数组
-
表达“具名属性集合”用对象;表达“有序集合”或“同质条目列表”用数组。
-
非必要别把对象强行塞进数组第 0 位取用,这会让语义与索引耦合。
嵌套与扁平
-
面向写多读少、需要领域边界清晰的,用嵌套(可读、语义自洽)。
-
面向分析/检索、读多写少的,用扁平(便于建索引和聚合)。
-
复杂场景可采用“外部扁平 + 内部嵌套”混合:对外一层扁平可检索,业务子对象内部保留语义结构。
示例
{
"user_id": "u_123",
"profile": {
"full_name": "Ada Lovelace",
"locale": "en-GB"
},
"roles": ["admin", "editor"],
"created_at": "2025-08-14T10:08:00Z",
"meta": { "version": 3, "source": "import" }
}
三、可兼容的演进策略
“向后兼容”优先级最高
-
新增字段不影响老读者;删除/重命名则高危。要删除时,先“双写+弃用标记(deprecated)”一个版本周期,再宣布下线。
-
缺失 vs 空值:缺失表示“未知/未提供”,null 表示“明确无值”。区分它们能显著减少歧义。
版本化
-
细粒度场景用能力探测(“如果有 field_x 就使用”);
-
严格契约用模式版本("schema_version": 2),或在 API 路径中标注版本(与载荷里的 "version" 各司其职)。
-
设计扩展位:预留 meta 或 x-* 命名空间存放非核心字段,避免污染主模型。
变更表达
-
部分更新优先考虑 JSON Patch(RFC 6902)或 JSON Merge Patch(RFC 7386),更小、更明确,可审计。
四、类型与格式:把坑填在设计阶段
布尔与枚举
-
明确枚举列表:例如 status: "pending" | "active" | "suspended",避免布尔地狱(is_ok, is_valid, is_ready 同时出现)。
-
复杂状态用枚举 + reason 细化。
数值与精度
-
金额/利率/大整数不要用二进制浮点。三种做法:
-
字符串传输("amount": "136500.25"),由消费端严格解析;
-
整数最小单位(分/厘),如 13650025;
-
定点小数(规定精度位数)。
-
选择一种并写进契约。
时间与时区
-
推荐 ISO 8601 UTC:"2025-08-14T10:08:00Z";
-
避免无时区的裸日期时间。仅日期用 "YYYY-MM-DD",仅时间用 "HH:MM:SS" 并说明时区上下文。
-
需要区间就用 start_at / end_at,避免复用单字段承载多义。
二进制与大文本
-
图片、PDF 不直接塞 base64(膨胀+内存压力),传 URL + 摘要哈希;需要内嵌时加上 media_type 与 sha256。
国际化(i18n)
-
文本多语言:要么多条记录分语言,要么结构化字段:
"title": { "zh-CN": "标题", "en-US": "Title" }
-
统一 locale 标记,遵循 BCP 47(如 zh-CN)。
五、归一化 vs 冗余:为读写成本做减法
-
强一致、高变更率:倾向归一化(用 id 关联,减少重复更新),但读取需要二次查询。
-
读多写少、聚合展示:允许适度冗余(复制少量快变字段),换取读取直达。
-
经验法则:冗余字段要标注来源与更新时间:"seller_name": "…", "seller_snapshot_at": "…",清楚“快照语义”。
六、错误与元信息:契约要友好
错误对象(避免只返回字符串)
{
"error": {
"code": "INVALID_ARGUMENT",
"message": "amount must be >= 0",
"details": [{ "field": "amount", "rule": "min", "min": 0 }]
},
"trace_id": "req-7f3c…"
}
-
给机器可解析的 code,给人可读的 message,加 trace_id 便于排障。
-
成功响应也建议有 meta:分页信息、总数、游标等。
分页/列表
-
大集合尽量游标(next_cursor)而不是页号(避免数据漂移)。
-
明确上限(如 limit<=100),防止滥用导致内存爆。
七、校验与文档:用工具把“约定俗成”变成“强约束”
JSON Schema(Draft 2020-12 等)
-
为每个公开结构提供 schema,并在 CI 中校验。
-
把示例和边界条件写进测试夹具。
-
推荐理念:宽进严出(输入端更宽容,输出端更严格)。
落地工具
-
Node.js:Ajv;Python:Pydantic/JsonSchema 校验;Java/Kotlin:Jackson + Bean Validation;Go:jsonschema + 自定义校验;Rust:serde + validator。
-
为 Schema 加注释(description),把文档生成自动化。
最小样例(片段)
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://example.com/schemas/order.json",
"title": "Order",
"type": "object",
"required": ["order_id", "items", "amount", "created_at"],
"properties": {
"order_id": { "type": "string", "pattern": "^ord_[a-zA-Z0-9]{8,}$" },
"items": {
"type": "array",
"minItems": 1,
"items": {
"type": "object",
"required": ["sku", "qty"],
"properties": {
"sku": { "type": "string" },
"qty": { "type": "integer", "minimum": 1 }
},
"additionalProperties": false
}
},
"amount": { "type": "string", "pattern": "^[0-9]+(\\\\.[0-9]{2})?$" },
"created_at": { "type": "string", "format": "date-time" }
},
"additionalProperties": false
}
八、性能与规模:从“请求一次”到“跑得稳”
体积控制与可流式
-
大列表使用 NDJSON/JSON Lines(一行一条记录)便于流式处理与增量写入。
-
合理分页、字段选择(fields=…)、与压缩(传输层开启 gzip/br)。
-
结果集可分层:summary(轻)与 details(重)分离。
顺序与确定性
-
JSON 对象键无顺序保证;若你需要签名/去重,使用 Canonical JSON/排序后的键 进行哈希。
-
对外宣称“顺序稳定”的数组,务必定义稳定排序键与并发写入策略。
九、安全与健壮性:别让 JSON 成为攻击面
-
数字溢出/精度丢失:JS 的 Number 是双精度浮点,大整数会丢精度。关键数值用字符串或 BigInt 方案。
-
原型污染:在 JS 反序列化时拒绝 __proto__、constructor 等魔法键。
-
字段白名单:反序列化到强类型对象时启用白名单/additionalProperties:false。
-
限流与尺寸:限制最大载荷与最大数组长度,避免 JSON 炸弹(深嵌套/巨数组)。
-
签名与校验:重要数据体可做 HMAC/签名,配合 canonical 化避免“重排”攻击。
十、一个从 0 到 1 的设计流程示例
以“订单导出服务”为例,你可以这样走:
目标与消费者:供前端分页查看、也供数据团队批量拉取。
结构选择:对外分页 API 用对象+数组;离线导出提供 NDJSON。
字段与语义:金额字符串两位小数;时间一律 UTC ISO 8601;商品条目为数组,qty 整数。
扩展与版本:根对象含 schema_version 与 meta;弃用字段先双写。
约束与文档:编写 JSON Schema,CI 校验,自动生成接口文档;提供最小样例与错误样例。
性能与安全:分页上限 100,数组长度上限 1000,返回体压缩,服务端设总超时;对导出任务结果做 canonical JSON + 哈希校验。
十一、易错与反模式速记
-
把金额用浮点数;
-
混用大小写命名;
-
一个字段多重语义(既放“创建时间”又偶尔放“更新时间”);
-
用 null 表示“缺失”和“明确无值”两种不同含义;
-
返回纯字符串错误信息、缺少错误码;
-
把大文件/图片直接 base64 塞进响应;
-
随意重命名字段、无版本策略;
-
让对象键顺序承担语义。
末尾清单(Checklist)
-
命名统一且语义明确
-
类型清晰:金额/大整数有精度策略;时间有时区
-
缺失 vs null 语义区分
-
结构选择:对象/数组/嵌套/扁平的取舍有理由
-
预留扩展位(meta/x-*),有版本/弃用策略
-
错误模型有 code/message/details/trace_id
-
分页与限流规则明确(页号或游标、上限)
-
JSON Schema 覆盖 & CI 校验 & 示例齐全
-
大数据量有 NDJSON/流式方案与压缩
-
安全:白名单、深度/大小限制、canonical + 签名(必要时)
评论前必须登录!
注册