Skip to content

表示与媒体类型

本章导读:客户端永远接触不到"资源本身",只能拿到资源的表示(Representation)——一段带媒体类型标签的字节流。本章讲清表示的构成、媒体类型(MIME)如何选择与注册、以及 JSON 作为默认表示时的结构性约定(包装、链接、时间、空值)。

1. 表示的三层结构

一次"获取文章"的响应里,表示由三部分自描述地组成(RFC 9110 §8):

http
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8   ← ① 元数据:这是什么
Content-Language: zh-CN                          ← ② 元数据:人类语言
Vary: Accept                                     ← ② 缓存键提示

{ "id": 42, "title": "……" }                       ← ③ 表示数据(payload)
  • 元数据(headers):格式、语言、编码、缓存策略、版本标签。
  • 表示数据:真正的字节流。
  • 表示是"资源的快照",不是资源本身——同一资源可有多种并存表示:JSON、HTML、PDF、图片缩略图。

2. 媒体类型(MIME Type)

媒体类型格式为 type/subtype[+suffix][;parameters](注册机制见 RFC 6838,IANA 维护登记簿)。

2.1 API 常用媒体类型速查

媒体类型用途规范
application/json默认请求/响应格式RFC 8259
application/problem+json标准错误响应RFC 9457
text/plaintext/html纯文本/网页表示RFC 9110 注册表
application/octet-stream二进制下载兜底RFC 9110
multipart/form-data文件上传RFC 7578
application/x-www-form-urlencoded表单RFC 9110
application/json-patch+jsonPATCH 差量指令RFC 6902
application/merge-patch+jsonPATCH 合并补丁RFC 7386
application/vnd.api+jsonJSON:API 规范格式jsonapi.org
application/hal+json超媒体(HATEOAS)hal.dev(社区草案)
application/geo+json地理数据RFC 7946
application/pdfimage/*文档/图片表示IANA 注册表

2.2 +json 结构化后缀(RFC 6839)

application/problem+json 的含义是:"它是 JSON,并附带 problem 语义"。好处:

  • 只认识 JSON 的客户端/中间件也能解析 body;
  • Accept 协商中可按结构化后缀匹配(Accept: application/*+json)。

自定义类型时请遵守:application/vnd.myapp.invoice+json——vnd. 前缀 + 私有树 + +json 后缀(见 §2.4)。

2.3 Content-Type 与 Accept 是一对

http
POST /articles HTTP/1.1
Content-Type: application/json     ← 我发给你的是 JSON(请求体格式)
Accept: application/json           ← 请你还给我 JSON(期望响应格式)
  • 客户端未带 Accept 时,按 */* 处理,服务器可返回默认表示。
  • 服务器无法满足 Accept 时返回 406 Not Acceptable(见内容协商)。
  • Content-Type 缺失或错误是 API 事故高频原因:多数框架会拒绝无 Content-Type 的 POST/PUT,或按错误解析器处理 body。

2.4 私有媒体类型的"树"

RFC 6838 定义媒体类型分四个树(tree):tree.name 形式:

前缀
标准树(无后缀)application/json
个人树personal.application/personal.x+json
供应商树vnd.application/vnd.github.raw+json
非注册树x.application/x.mything+json

内部 API 若要自定义类型,规范写法是 application/vnd.{公司}.{资源}+json不必真的去 IANA 注册。同时注意 RFC 6648:HTTP 头部命名已不再使用 X- 前缀。

3. JSON 表示的结构约定

RFC 8259 只规定语法,不规定"一个资源该长什么样"。以下是业界收敛出的结构约定(本教程后续章节全部遵循并展开):

3.1 顶层形态:对象优先于数组

jsonc
// 单资源:顶层对象
{ "id": 42, "title": "…" }

// 集合资源:顶层对象包数据 + 元数据,而不是裸数组
{
  "items": [ { "id": 42 }, { "id": 43 } ],
  "total": 2,
  "page": 1,
  "pageSize": 20
}

裸数组的缺陷:无法携带分页/总数信息;未来加字段即破坏兼容;某些老 JSONP 场景有安全隐患。

3.2 时间:RFC 3339(profile 自 ISO 8601)

json
{ "createdAt": "2026-09-18T08:30:00Z", "updatedAt": "2026-09-18T10:12:31+08:00" }
  • 统一使用 UTC(Z)或带显式偏移量;禁止本地时间字符串 2026-09-18 08:30:00
  • 日期与时间分离的字段用 YYYY-MM-DD(如生日 birthDate)。
  • 不要返回 Unix 秒数裸数字作为默认方案——可读性差且秒/毫秒歧义常见。

3.3 空值的语义要区分

场景表示含义
字段无值"nickname": null 或按团队约定省略字段明确"没有"
未修改(PATCH)字段不出现在 body 中null 严格区分(见 PATCH 一章)
集合为空"comments": []不是 null,保持类型稳定

TIP

"省略字段 = 无值"与"省略 = 未修改"在 PATCH 里冲突,因此团队规范必须写明:资源 GET 表示里稳定出现的字段,PATCH 时以媒体类型语义为准(merge-patch 中 null 表示删除,见 RFC 7386)。

3.4 数字与精度

  • 雪花 ID/长整型超过 2^53 时,以字符串传输(JS number 精度丢失是经典事故):"orderId": "1873201933445566778"
  • 金额用最小货币单位整数(分)或字符串小数,禁止浮点"amount": "19.90"1990

3.5 链接字段

即使是 Level 2 API,也建议返回资源的规范 URI,方便客户端直接跳转:

json
{ "id": 42, "author": { "id": 7, "link": "/authors/7" } }

完全体即 HATEOAS 的 _links(HAL 格式),见理查森成熟度模型 Level 3。

4. 字符编码与传输

  • JSON 默认 UTF-8(RFC 8259 强制互操作要求);HTTP/1.1 文本类型需 charset=utf-8(HTTP/2 及 JSON 场景可省略,但加上更稳妥)。
  • 响应压缩用 Content-Encoding: gzip/br(RFC 9110 §8.4),与 Content-Type 是两个维度,别混淆。
  • 范围请求(视频/断点续传)返回 206 Partial Content + Content-Range(RFC 9110 §14)。

5. 本章小结

  • 表示 = 元数据(headers)+ 数据(payload);同一资源可有多种表示,由内容协商选择。
  • 媒体类型是有登记簿的语言:application/json 是通用语,+json 后缀携带专门语义,错误响应用 application/problem+json
  • JSON 结构四要素:对象顶层包集合、RFC 3339 时间、区分 null/省略、大数与金额用字符串。

6. 下一步

表示"怎么变"由方法决定——进入 REST 的动词表 → HTTP 方法语义