API 响应 JSON 格式化最佳实践
← 返回博客
API 响应 JSON 格式化最佳实践
· 5 分钟阅读
统一响应结构:成功与失败的一致性
良好的 API 设计的核心原则之一是响应结构的一致性。无论请求成功还是失败,响应的外层结构应该保持相同的形式,让客户端能够用统一的方式处理所有响应。最常见的统一结构是在最外层包裹一个信封(Envelope)对象,包含状态信息和数据:
对于成功的列表响应,这种包裹结构尤为有价值:它既提供了实际数据,又包含了分页信息、请求标识符等元数据,客户端无需解析响应头就能获得完整上下文。一旦团队确定了这个结构,所有端点都应严格遵守,不应在某些端点直接返回数组,而在其他端点返回对象。
// 成功响应 / Success response
{
"success": true,
"data": {
"user": {
"id": 12345,
"name": "Alice",
"email": "[email protected]"
}
},
"meta": {
"requestId": "req_abc123",
"timestamp": "2025-01-01T00:00:00Z"
}
}
// 错误响应 / Error response
{
"success": false,
"error": {
"code": "USER_NOT_FOUND",
"message": "The requested user does not exist",
"details": {"userId": 99999}
},
"meta": {
"requestId": "req_def456",
"timestamp": "2025-01-01T00:00:01Z"
}
}
字段命名规范:camelCase vs snake_case
API 字段命名风格的选择对客户端开发体验有直接影响。camelCase(如 firstName、createdAt)在 JavaScript 生态中是自然选择,因为 JSON 本身源自 JavaScript,与 JS 对象的属性命名约定一致。大多数现代公共 API(Twitter/X、GitHub、Stripe)都采用 camelCase。
snake_case(如 first_name、created_at)在 Python、Ruby、数据库字段命名中更为常见,服务器端用这类语言时也更自然。如果 API 主要面向 Python 客户端,snake_case 可以减少一层转换工作。最重要的原则是:在整个 API 中保持一致,不要混用两种风格。
分页响应格式
分页是列表类 API 最重要的设计决策之一。主流方案有两种:基于页码的分页(offset-based)和基于游标的分页(cursor-based)。基于页码的分页更直观,适合数据集相对固定的场景;基于游标的分页适合数据量大、实时性强的场景(如社交媒体信息流)。无论采用哪种方式,都应该在响应中包含完整的分页元数据:
// 基于页码的分页 / Offset-based pagination
{
"success": true,
"data": [...],
"pagination": {
"page": 2,
"pageSize": 20,
"total": 157,
"totalPages": 8,
"hasNext": true,
"hasPrev": true
}
}
// 基于游标的分页 / Cursor-based pagination
{
"success": true,
"data": [...],
"pagination": {
"cursor": "eyJpZCI6MTIzfQ==",
"nextCursor": "eyJpZCI6MTQzfQ==",
"hasMore": true,
"limit": 20
}
}
错误响应的标准化
一致且信息丰富的错误响应是 API 可用性的关键。错误响应应包含:机器可读的错误代码(code)用于程序化处理、人类可读的错误消息(message)用于调试、可选的字段级验证错误(fieldErrors)和详情(details)。错误代码应使用有意义的字符串,而非纯数字,这样即使没有文档也能理解大概含义:
HTTP 状态码和 JSON 错误体应该协同工作:400 对应客户端错误(如参数缺失、格式错误),401 对应未认证,403 对应无权限,404 对应资源不存在,422 对应业务验证失败,500 对应服务器内部错误。不要所有错误都返回 200 状态码然后在 JSON 体里区分——这会让客户端难以使用标准 HTTP 客户端库的错误处理机制。
{
"success": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "Request validation failed",
"fieldErrors": [
{
"field": "email",
"code": "INVALID_FORMAT",
"message": "Must be a valid email address"
},
{
"field": "age",
"code": "OUT_OF_RANGE",
"message": "Must be between 0 and 150"
}
]
}
}
日期和时间字段的格式化
日期时间是 API 中最容易出现格式不一致问题的字段类型。强烈推荐:所有日期时间字段统一使用 ISO 8601 格式(2025-01-01T00:00:00Z),始终包含时区信息(推荐 UTC,以 Z 结尾),Unix 时间戳(秒或毫秒)适合需要精确时间比较和排序的场景。
避免的做法:不要使用本地化日期字符串(如 "Jan 1, 2025"),这样的格式客户端难以解析;不要在同一 API 中混用不同日期格式;如果 API 有国际用户,避免使用纯数字的月日格式(01/02/2025 在不同地区含义不同)。对于只有日期没有时间的字段(如生日),使用 YYYY-MM-DD 格式。
空值和缺失字段的处理
在 API 响应中,null 值和完全省略字段是两种不同的语义。一般原则:null 表示字段存在但没有值(如用户未设置头像,"avatar": null);完全省略字段表示该字段不适用于此对象(如某类型用户没有该字段的概念)。在同一个 API 中,应该在团队内达成明确共识并记录下来。
常见的错误是对不同的"空"情况返回不一致的表示:有时返回 null,有时返回空字符串 "",有时省略字段。这会让客户端需要写复杂的防御代码。最佳实践是:始终返回所有在 Schema 中声明的字段(即使是 null),这样客户端可以依赖稳定的结构;只对真正"不存在"的概念才省略字段,并在文档中明确说明。
大数字和精度问题
当 API 返回超过 JavaScript 安全整数范围(Number.MAX_SAFE_INTEGER = 9007199254740991)的整数时,会遇到精度丢失问题。最常见的场景是使用 64 位整数 ID(雪花 ID、Twitter ID 等)——这些 ID 在 JavaScript 的 JSON.parse() 处理时会失去精度,导致 ID 被错误地识别。
解决方案:将大整数 ID 同时以字符串和数字形式返回(Twitter 的历史做法),或者直接只返回字符串形式。虽然这使 JSON 变得稍微冗余,但避免了所有下游客户端都需要特殊处理的问题。对于 Go 后端,使用 json:",string" 标签可以自动将 int64 字段序列化为字符串;对于 Java 后端,可以配置 Jackson 将 Long 序列化为字符串。
API 版本化与响应格式演进
随着业务发展,API 响应格式不可避免地需要演进。常见的版本化策略:URL 路径版本(/v1/users、/v2/users)最清晰直观,易于文档化;请求头版本(Accept: application/vnd.api.v2+json)更符合 REST 理念但实现更复杂;查询参数版本(?version=2)简单但不够优雅。
在不引入新版本的情况下向后兼容地演进响应格式:可以安全地添加新字段(客户端应忽略未知字段);不能删除或重命名现有字段;不能更改现有字段的数据类型。对于破坏性变更(breaking change),必须引入新的 API 版本并同时维护旧版本一段时间,给客户端足够的迁移窗口。可以在响应头或 JSON 响应体中提供 deprecation 信息,提示客户端尽快迁移。
立即尝试在线工具,无需安装,免费使用。
打开工具 →
立即免费使用相关工具
免费使用 →