Appearance
表示与媒体类型
本章导读:客户端永远接触不到"资源本身",只能拿到资源的表示(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/plain、text/html | 纯文本/网页表示 | RFC 9110 注册表 |
application/octet-stream | 二进制下载兜底 | RFC 9110 |
multipart/form-data | 文件上传 | RFC 7578 |
application/x-www-form-urlencoded | 表单 | RFC 9110 |
application/json-patch+json | PATCH 差量指令 | RFC 6902 |
application/merge-patch+json | PATCH 合并补丁 | RFC 7386 |
application/vnd.api+json | JSON:API 规范格式 | jsonapi.org |
application/hal+json | 超媒体(HATEOAS) | hal.dev(社区草案) |
application/geo+json | 地理数据 | RFC 7946 |
application/pdf、image/* | 文档/图片表示 | 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 方法语义