用户登录

技术文章

外部链接将在新标签页打开。

JSON、表单与图片接口的响应规范:先定解析分派,再谈字段

一句话回答

三种响应类型的分歧首先在解析入口:客户端先看状态码与 Content-Type,再决定用哪个解析器。文章建议把“2xx 用声明的业务类型、非 2xx 一律 JSON 错误体”写成契约,并分别说明表单的多值与编码问题、图片响应的缓存与条件请求,最后给出一份跨类型一致性清单和一个未执行的分派示例。

JSON、表单与图片接口的响应设计,真正的分歧不在字段命名,而在解析入口:客户端拿到响应后,第一步看的是状态码和 Content-Type,据此决定用哪个解析器。规范应当先固定这条分派规则,再讨论字段与错误码。本文给出的是可落地的设计约定,不是对某个 API.PLUS 端点的实测结果;具体端点的字段、状态码与缓存策略仍需按各自文档核实。

一、把 Content-Type 当作分派开关#

响应头里的媒体类型决定客户端走哪条解析路径。JSON 用 application/json;表单常用 application/x-www-form-urlencoded 或 multipart/form-data;图片应给出具体类型,如 image/png、image/jpeg,不要用 application/octet-stream 代替,否则客户端只能靠猜。上述类型名是通行做法,本文未联网核验规范条文,落地前应以所用框架与官方文档为准。

分派顺序建议固定为:先判状态码类别,再判媒体类型,最后才读 body。顺序反过来,就会出现“先解析、后报错”的无效工作。

二、错误响应在三种类型下必须同形#

JSON 接口返回 JSON 错误体是常规做法;真正的坑在图片接口:出错时如果仍带着图片的 Content-Type,body 里却是 JSON 文本,客户端的图片解码器会直接失败,错误信息也被吞掉。表单接口同理,出错时返回一段 HTML 或纯文本,SDK 侧很难结构化处理。

建议写进契约的一条规则:2xx 返回声明的业务类型,非 2xx 一律返回 JSON 错误体,并把 Content-Type 改为 application/json。错误体的字段名在三种接口间保持一致,例如统一的 code、message、request_id。

结构化错误体的字段设计可参考 Problem Details(RFC 9457)这类公开约定,但本文未核验其条文细节与版本状态,采用前请自行对照原始文档。

三、表单响应的两个易错点#

  • 字符集:Content-Type 中应显式带 charset,避免客户端用默认编码解出乱码。
  • 多值与空值:表单靠重复键表达多值,字段顺序是否保留取决于解析实现,这一点待核实;“字段缺失”和“字段存在但为空”在表单里容易混淆,需要在契约里写明各自含义。

表单响应的好处是浏览器可直接渲染、便于人工排查;代价是类型信息弱,数字和布尔值都要由客户端二次转换。对以程序消费为主的接口,优先选 JSON。

四、图片响应:元数据放头,不塞进 body#

二进制响应不要在 body 里混入描述信息。宽高、格式、内容校验值可以放在响应头,或另开一个 JSON 元数据端点。缓存交给 Cache-Control 与 ETag,条件请求命中时返回 304 且不带 body;客户端必须能处理“没有 body 的成功响应”,这条要在文档里写明。相关语义的准确表述待核实规范原文。

五、跨类型一致性清单#

项目JSON表单图片
成功 Content-Typeapplication/json带 charset 的表单类型具体图片类型
成功 body对象键值对,多值用重复键纯二进制
错误响应统一 JSON 错误体,字段同名,携带 request_id
缓存视数据性质定 TTL一般不强缓存ETag + 条件请求

六、一个未执行的分派示例#

下面的代码仅示意分派顺序,未在任何服务上执行,也不代表任何真实端点的行为。

// 未执行的示例:按状态码 → 媒体类型 → body 的顺序分派
async function readResponse(res) {
  const type = (res.headers.get('content-type') || '').toLowerCase();

  if (!res.ok) {
    // 契约:非 2xx 一律 JSON 错误体;解析失败要兜底
    try { return { kind: 'error', body: await res.json() }; }
    catch { return { kind: 'error', body: { message: `HTTP ${res.status}` } }; }
  }
  if (type.includes('application/json')) return { kind: 'json', body: await res.json() };
  if (type.includes('application/x-www-form-urlencoded')) {
    return { kind: 'form', body: new URLSearchParams(await res.text()) };
  }
  if (type.startsWith('image/')) return { kind: 'image', body: await res.blob() };
  return { kind: 'unknown', body: await res.arrayBuffer() };
}

注意 304 会进入成功分支但没有 body,调用方需要单独处理,否则 res.json() 会抛错。

七、适用范围#

本次材料只有主题标题,没有可核验的接口文档或测试记录。因此上文属于设计建议而非规范复述:状态码的具体取值、字段命名、缓存时长都应视为待核实,需以目标服务的实际文档与一次可复现的调用记录为准。

作者:沐小悠

本文约 7 分钟读完 · 关键词:响应设计、Content-Type、错误响应、二进制响应、表单解析

更多文章 GET 示例接口

「」标签下的其他文章