最新规范 (v1.1)
状态
本页面介绍了 JSON:API 的最新发布版本,当前为版本 1.1。JSON:API 的新版本将始终 **向后兼容**,采用“永不移除,只添加”的策略。新增内容可以在我们的 讨论论坛 中提出。
如果您在规范文本中发现错误,或者编写了实现,请通过在我们的 GitHub 仓库 中创建问题或拉取请求来告知我们。
简介
JSON:API 是一个规范,用于规定客户端如何请求获取或修改资源,以及服务器如何响应这些请求。JSON:API 可以轻松地使用 扩展 和 配置文件 进行扩展。
JSON:API 旨在最大限度地减少客户端和服务器之间请求的数量和数据传输量。这种效率是在不影响可读性、灵活性或可发现性的情况下实现的。
JSON:API 要求使用 JSON:API 媒体类型 (application/vnd.api+json
) 来交换数据。
语义
本规范定义的所有文档成员、查询参数和处理规则统称为“规范语义”。
某些文档成员、查询参数和处理规则留给实现者自行定义。这些被称为“实现语义”。
所有其他语义保留用于本规范将来可能的使用。
惯例
本文档中的关键字“必须”、“不得”、“必需”、“应”、“不应”、“建议”、“不建议”、“可”、“可选”的解释如 BCP 14 [RFC2119] [RFC8174] 所述,但仅限于它们全部大写时,如本文档所示。
JSON:API 媒体类型
JSON:API 媒体类型是 application/vnd.api+json
.
媒体类型参数
JSON:API 规范支持两个媒体类型参数:ext
和 profile
,分别用于指定 扩展 和 配置文件。
注意:媒体类型参数是媒体类型可以附带的额外信息。例如,在标头
Content-Type: text/html;charset="utf-8"
中,媒体类型是text/html
,而charset
是一个参数。
扩展
扩展提供了一种方法,通过定义额外的 规范语义 来“扩展”基础规范。
扩展不能更改或删除规范语义,也不能指定实现语义。
配置文件
配置文件提供了一种方法,用于在实现之间共享规范的特定用法。
配置文件可以指定 实现语义,但不能更改、添加或删除规范语义。
媒体类型参数规则
JSON:API 媒体类型 **不得** 指定除 ext
和 profile
以外的任何媒体类型参数。 ext
参数用于支持 扩展,profile
参数用于支持 配置文件。
扩展和配置文件都由一个 URI 唯一标识。访问扩展或配置文件的 URI **应该** 返回描述其用法的文档。 ext
和 profile
参数的值 **必须** 分别等于一个用空格分隔 (U+0020 SPACE,“ ”) 的扩展或配置文件 URI 列表。
注意:在序列化
ext
或profile
媒体类型参数时,HTTP 规范要求参数值用引号 (U+0022 QUOTATION MARK,“"”) 括起来。
扩展规则
扩展 **可以** 强制执行额外的处理规则或进一步限制,并且 **可以** 定义新的对象成员,如下所述。
扩展 **不得** 减少或删除本规范或其他扩展中定义的任何处理规则、限制或对象成员要求。
扩展 **可以** 在本规范定义的文档结构中定义新成员。扩展成员名称的规则将在 下面 讨论。
扩展 **可以** 定义新的查询参数。扩展定义的查询参数的规则将在 下面 讨论。
当扩展定义新的查询参数或文档成员时,扩展 **必须** 定义一个命名空间,以确保扩展永远不会与当前或将来的规范版本冲突。命名空间 **必须** 满足以下所有条件
- 命名空间 **必须** 包含至少一个字符。
- 命名空间 **必须** 仅包含以下字符
- U+0061 到 U+007A,“a-z”
- U+0041 到 U+005A,“A-Z”
- U+0030 到 U+0039,“0-9”
扩展 **不得** 定义多个命名空间。任何给定扩展用于所有查询参数和文档成员的命名空间 **必须** 相同。
在以下示例中,一个名为 version
的扩展已指定一个资源对象成员 version:id
来支持每个资源的版本控制。此成员可能如下所示
HTTP/1.1 200 OK
Content-Type: application/vnd.api+json;ext="https://jsonapi.fullstack.org.cn/ext/version"
// ...
{
"type": "articles",
"id": "1",
"version:id": "42",
"attributes": {
"title": "Rails is Omakase"
}
}
// ...
配置文件规则
配置文件使用的规则由 RFC 6906 规定。
配置文件 **可以** 定义保留给实现者的文档成员和处理规则。
配置文件 **不得** 定义任何查询参数,除了 实现特定的查询参数。
配置文件 **不得** 更改或删除本规范或 扩展 定义的任何处理规则。但是,配置文件 **可以** 为查询参数定义处理规则,这些查询参数的处理规则保留给实现者自行定义。
例如,配置文件可以定义解释 filter
查询参数 的规则,但不能指定 include
查询参数 中的关系名称是用空格分隔还是用点分隔。
与扩展不同,配置文件不需要为文档成员定义命名空间,因为配置文件不能定义规范语义,因此不会与当前或将来的规范版本冲突。但是,配置文件可能会与其他配置文件冲突。因此,实现者有责任确保他们不支持冲突的配置文件。
在以下示例中,一个配置文件定义了一个 timestamps
属性。根据配置文件,该属性必须是一个包含 created
成员和 modified
成员的对象,并且这些成员的值必须使用 RFC 3339 格式。应用此配置文件后,响应可能如下所示
HTTP/1.1 200 OK
Content-Type: application/vnd.api+json;profile="https://example.com/resource-timestamps"
// ...
{
"type": "articles",
"id": "1",
"attributes": {
"title": "Rails is Omakase",
"timestamps": {
"created": "2020-07-21T12:09:00Z",
"modified": "2020-07-30T10:19:01Z"
}
}
}
// ...
内容协商
通用职责
客户端和服务器 **必须** 使用 JSON:API 媒体类型在 Content-Type
标头中发送所有 JSON:API 负载。
客户端和服务器 **必须** 在 Content-Type
标头中指定 ext
媒体类型参数,当它们将一个或多个扩展应用于 JSON:API 文档时。
客户端和服务器 **必须** 在 Content-Type
标头中指定 profile
媒体类型参数,当它们将一个或多个配置文件应用于 JSON:API 文档时。
客户端职责
在处理 JSON:API 响应文档时,客户端 **必须** 忽略服务器 Content-Type
标头中除 ext
和 profile
参数以外的任何参数。
客户端 **可以** 在 Accept
标头中使用 ext
媒体类型参数,以要求服务器将所有指定的扩展应用于响应文档。
客户端 **可以** 在 Accept
标头中使用 profile
媒体类型参数,以请求服务器将一个或多个配置文件应用于响应文档。
注意:客户端允许在
Accept
标头中发送多个可接受的媒体类型,包括 JSON:API 媒体类型的多个实例。这允许客户端请求ext
和profile
媒体类型参数的不同组合。客户端可以使用 质量值 来指示某些组合不如其他组合更可取。未指定 qvalue 的媒体类型彼此之间同样可取,无论其顺序如何,并且始终被认为比 qvalue 小于 1 的媒体类型更可取。
服务器职责
如果请求在 Content-Type
标头中指定了 JSON:API 媒体类型,并且该媒体类型包含除 ext
或 profile
以外的任何媒体类型参数,服务器 **必须** 响应 415 Unsupported Media Type
状态代码。
如果请求在 Content-Type
标头中指定了 JSON:API 媒体类型的实例,该实例由 ext
媒体类型参数修改,并且该参数包含不支持的扩展 URI,服务器 **必须** 响应 415 Unsupported Media Type
状态代码。
注意:不支持本规范的版本 1.1 的 JSON:API 服务器将响应
415 Unsupported Media Type
客户端错误,如果存在ext
或profile
媒体类型参数。
如果请求的 Accept
头部包含 JSON:API 媒体类型实例,服务器 **必须** 忽略该媒体类型的实例,这些实例被除 ext
或 profile
以外的媒体类型参数修改。如果该媒体类型的所有实例都使用除 ext
或 profile
以外的媒体类型参数修改,服务器 **必须** 以 406 Not Acceptable
状态码响应。如果该媒体类型的每个实例都使用 ext
参数修改,并且每个实例都包含至少一个不受支持的扩展 URI,服务器 **必须** 也以 406 Not Acceptable
响应。
如果收到 profile
参数,服务器 **应该** 尝试将任何请求的配置文件应用于其响应。服务器 **必须** 忽略它不识别的任何配置文件。
注意:上述规则保证了客户端和服务器之间在扩展方面的一致性,而配置文件的应用则由服务器自行决定。
支持 ext
或 profile
媒体类型参数的服务器 **应该** 使用 Vary
头部,其中 Accept
是其值之一。这适用于已应用和未应用任何 配置文件 或 扩展 的响应。
注意:一些 HTTP 中介(例如 CDN)可能会忽略
Vary
头部,除非专门配置为尊重它。
文档结构
本节描述 JSON:API 文档的结构,该结构由 JSON:API 媒体类型 标识。JSON:API 文档在 JavaScript 对象表示法 (JSON) [RFC8259] 中定义。
虽然请求和响应文档都使用相同的媒体类型,但某些方面只适用于其中之一。这些差异在下面说明。
扩展 **可以** 在文档结构中定义新的成员。这些成员 **必须** 符合 下面 指定的命名要求。
除非另有说明,否则本规范或任何应用的扩展定义的对象 **不能** 包含任何其他成员。客户端和服务器实现 **必须** 忽略不符合要求的成员。
注意:这些条件允许本规范通过增量更改进行演变。
最顶层
JSON 对象 **必须** 位于每个包含数据的 JSON:API 请求和响应文档的根部。此对象定义文档的“最顶层”。
文档 **必须** 包含以下最顶层成员中的至少一个
成员 data
和 errors
**不能** 在同一个文档中共存。
文档 **可以** 包含以下任何最顶层成员
如果文档不包含最顶层的 data
键,则 included
成员 **不能** 存在。
最顶层的 链接对象 **可以** 包含以下成员
self
:生成当前响应文档的 链接。如果文档应用了扩展或配置文件,则此链接 **应该** 由一个 链接对象 表示,该对象使用type
目标属性指定具有所有适用参数的 JSON:API 媒体类型。related
:当主要数据表示资源关系时,一个 相关资源链接。describedby
:一个指向当前文档的描述文档(例如 OpenAPI 或 JSON Schema)的 链接。- 主要数据的 分页 链接。
注意:最顶层
links
对象中的self
链接允许客户端刷新当前响应文档表示的数据。客户端应该能够使用提供的链接,而无需应用任何其他信息。因此,链接必须包含客户端提供的用于生成响应文档的查询参数。这包括但不限于用于 [包含相关资源][fetching resources]、[稀疏字段集][fetching sparse fieldsets]、[排序][fetching sorting]、[分页][fetching pagination] 和 [过滤][fetching filtering] 的查询参数。
文档的“主要数据”是请求目标资源或资源集合的表示。
主要数据 **必须** 是以下之一
例如,以下主要数据是单个资源对象
{
"data": {
"type": "articles",
"id": "1",
"attributes": {
// ... this article's attributes
},
"relationships": {
// ... this article's relationships
}
}
}
以下主要数据是引用同一资源的单个 资源标识符对象
{
"data": {
"type": "articles",
"id": "1"
}
}
资源的逻辑集合 **必须** 表示为数组,即使它只包含一项或为空。
资源对象
“资源对象”出现在 JSON:API 文档中,以表示资源。
资源对象 **必须** 包含以下最顶层成员中的至少一个
id
type
例外:当资源对象起源于客户端,并且表示要在服务器上创建的新资源时,id
成员不是必需的。在这种情况下,客户端 **可以** 包含一个 lid
成员,以在文档中按 type
本地 唯一标识资源。
此外,资源对象 **可以** 包含以下任何最顶层成员
attributes
:一个 属性对象,表示资源数据的某些部分。relationships
:一个 关系对象,描述资源与其他 JSON:API 资源之间的关系。links
:一个 链接对象,包含与资源相关的链接。meta
:一个 元数据对象,包含有关资源的非标准元信息,这些信息不能表示为属性或关系。
以下是文章(即类型为“articles”的资源)在文档中的显示方式
// ...
{
"type": "articles",
"id": "1",
"attributes": {
"title": "Rails is Omakase"
},
"relationships": {
"author": {
"links": {
"self": "/articles/1/relationships/author",
"related": "/articles/1/author"
},
"data": { "type": "people", "id": "9" }
}
}
}
// ...
标识
如上所述,每个 资源对象 **必须** 包含一个 type
成员。每个资源对象 **也必须** 包含一个 id
成员,除非资源对象起源于客户端,并且表示要在服务器上创建的新资源。如果由于此例外而省略了 id
,则 **可以** 包含一个 lid
成员,以按 type
本地 唯一标识资源,在文档中。lid
成员的值 **必须** 与文档中每个资源表示形式(包括 资源标识符对象)的值相同。
id
、type
和 lid
成员的值 **必须** 是字符串。
在给定的 API 中,每个资源对象的 type
和 id
对 **必须** 标识单个唯一的资源。(服务器控制的 URI 集,或多个充当一个的服务器,构成一个 API。)
type
成员用于描述共享共同属性和关系的 资源对象。
type
成员的值 **必须** 遵守与 成员名称 相同的约束。
注意:本规范与词形变化规则无关,因此
type
的值可以是复数或单数。但是,在整个实现过程中应该始终使用相同的值。
字段
资源对象 的字段 **必须** 与彼此以及 type
和 id
共享一个公共命名空间。换句话说,资源不能具有相同名称的属性和关系,也不能具有名为 type
或 id
的属性或关系。
属性
attributes
键的值 **必须** 是一个对象(“属性对象”)。属性对象的成员(“属性”)表示定义它的 资源对象 的信息。
属性可以包含任何有效的 JSON 值,包括涉及 JSON 对象和数组的复杂数据结构。
引用相关资源的键(例如 author_id
)**不应该** 作为属性出现。相反,**应该** 使用 关系。
关系
键relationships
的值**必须**是一个对象(“关系对象”)。关系对象的每个成员代表从定义它的资源对象到其他资源对象的“关系”。
关系可以是一对一或一对多。
关系的名称由其键给出。该键下的值**必须**是一个对象(“关系对象”)。
表示一对多关系的关系对象**可以**在links
成员下包含分页链接,如下所述。关系对象中的任何分页链接**必须**对关系数据进行分页,而不是对相关资源进行分页。
相关资源链接
“相关资源链接”提供访问资源对象 的途径,这些对象在关系中链接。获取时,相关资源对象将作为响应的主要数据返回。
例如,article
的comments
关系可以指定一个链接,当通过GET
请求检索时,该链接将返回评论资源对象的集合。
如果存在,相关资源链接**必须**引用一个有效的 URL,即使关系当前未与任何目标资源相关联。此外,相关资源链接**不能**因为其关系的内容发生变化而改变。
资源链接
复合文档中的资源链接允许客户端将所有包含的资源对象链接在一起,而无需通过链接获取任何 URL。
资源链接**必须**用以下其中一种方式表示
注意:规范并没有对单对多关系链接数组中资源标识符对象的顺序赋予意义,尽管实现可能会这样做。资源标识符对象的数组可以表示有序或无序的关系,并且两种类型可以在一个响应对象中混合使用。
例如,以下文章与一个author
相关联
// ...
{
"type": "articles",
"id": "1",
"attributes": {
"title": "Rails is Omakase"
},
"relationships": {
"author": {
"links": {
"self": "http://example.com/articles/1/relationships/author",
"related": "http://example.com/articles/1/author"
},
"data": { "type": "people", "id": "9" }
}
},
"links": {
"self": "http://example.com/articles/1"
}
}
// ...
author
关系包含关系本身的链接(允许客户端直接更改相关作者)、一个用于获取资源对象的相关资源链接以及链接信息。
资源链接
如果存在,此链接对象**可以**包含一个self
链接,用于标识资源对象表示的资源。
// ...
{
"type": "articles",
"id": "1",
"attributes": {
"title": "Rails is Omakase"
},
"links": {
"self": "http://example.com/articles/1"
}
}
// ...
服务器**必须**对指定 URL 的GET
请求做出响应,该响应包括将资源作为主要数据。
资源标识符对象
“资源标识符对象”是一个标识单个资源的对象。
“资源标识符对象”**必须**包含一个type
成员。它**必须**也包含一个id
成员,除非它代表要创建到服务器上的新资源。在这种情况下,**必须**包含一个lid
成员,用于标识新资源。
id
、type
和 lid
成员的值 **必须** 是字符串。
“资源标识符对象”**可以**还包含一个meta
成员,其值是一个元对象,包含非标准元信息。
复合文档
服务器**可以**允许响应包含相关资源以及请求的主要资源。此类响应称为“复合文档”。
在复合文档中,所有包含的资源**必须**表示为顶级included
成员中的资源对象 的数组。
每个包含的资源对象**必须**通过从文档的主要数据开始的关系链来标识。这意味着复合文档需要“完整链接”,并且没有任何资源对象可以在没有与文档的主要数据的直接或间接关系的情况下包含在内。
对完整链接要求的唯一例外是,当关系字段由于客户端请求的稀疏字段集而被排除在本来应该包含链接数据的情况下。
包含多个包含关系的完整示例文档
{
"data": [{
"type": "articles",
"id": "1",
"attributes": {
"title": "JSON:API paints my bikeshed!"
},
"links": {
"self": "http://example.com/articles/1"
},
"relationships": {
"author": {
"links": {
"self": "http://example.com/articles/1/relationships/author",
"related": "http://example.com/articles/1/author"
},
"data": { "type": "people", "id": "9" }
},
"comments": {
"links": {
"self": "http://example.com/articles/1/relationships/comments",
"related": "http://example.com/articles/1/comments"
},
"data": [
{ "type": "comments", "id": "5" },
{ "type": "comments", "id": "12" }
]
}
}
}],
"included": [{
"type": "people",
"id": "9",
"attributes": {
"firstName": "Dan",
"lastName": "Gebhardt",
"twitter": "dgeb"
},
"links": {
"self": "http://example.com/people/9"
}
}, {
"type": "comments",
"id": "5",
"attributes": {
"body": "First!"
},
"relationships": {
"author": {
"data": { "type": "people", "id": "2" }
}
},
"links": {
"self": "http://example.com/comments/5"
}
}, {
"type": "comments",
"id": "12",
"attributes": {
"body": "I like XML better"
},
"relationships": {
"author": {
"data": { "type": "people", "id": "9" }
}
},
"links": {
"self": "http://example.com/comments/12"
}
}]
}
复合文档 **不能**包含多个资源对象,这些对象对应于相同的type
和id
对。
注意:在一个文档中,可以将
type
和id
视为一个复合键,该键在文档的其他部分中唯一地引用资源对象。
注意:对于不包含
id
成员但包含lid
的资源,lid
足以建立资源身份,从而在整个文档中的资源对象和资源标识符对象之间建立链接。
注意:这种方法确保即使同一资源被多次引用,也会返回具有每个响应的单个规范资源对象。
元信息
在指定的情况下,可以使用meta
成员来包含非标准元信息。meta
成员的值**必须**是一个对象(“元对象”)。
任何成员**可以**在meta
对象中指定。
例如
{
"meta": {
"copyright": "Copyright 2015 Example Corp.",
"authors": [
"Yehuda Katz",
"Steve Klabnik",
"Dan Gebhardt",
"Tyler Kellen"
]
},
"data": {
// ...
}
}
链接
在指定的情况下,可以使用links
成员来表示链接。此成员的值**必须**是一个对象(“链接对象”)。
- 一个字符串,其值为指向链接目标的 URI 引用 [RFC3986 第 4.1 节],
- 一个链接对象或
null
,如果链接不存在。
链接的关系类型**应该**从链接的名称推断,除非链接是链接对象并且链接对象具有rel
成员。
在下面的示例中,self
链接是一个字符串,而related
链接是一个链接对象。related
链接对象提供了有关目标相关资源集合的更多信息,以及一个用作该集合的描述文档的模式
"links": {
"self": "http://example.com/articles/1/relationships/comments",
"related": {
"href": "http://example.com/articles/1/comments",
"title": "Comments",
"describedby": "http://example.com/schemas/article-comments",
"meta": {
"count": 10
}
}
}
链接对象
“链接对象”是一个表示网络链接的对象。
链接对象**必须**包含以下成员
href
:一个字符串,其值为指向链接目标的 URI 引用 [RFC3986 第 4.1 节]。
链接对象**可以**还包含以下任何成员
rel
:一个字符串,表示链接的关系类型。该字符串**必须**是有效的链接关系类型。describedby
:一个指向链接目标的描述文档(例如,OpenAPI 或 JSON Schema)的链接。title
:一个字符串,用作链接目标的标签,以便可以用作人类可读的标识符(例如,菜单条目)。type
:一个字符串,表示链接目标的媒体类型。hreflang
:一个字符串或字符串数组,表示链接目标的语言(或语言)。字符串数组表示链接的目标在多种语言中可用。每个字符串**必须**是一个有效的语言标签 [RFC5646]。meta
:一个元对象,包含关于链接的非标准元信息。
注意:
type
和hreflang
成员只是提示;当实际跟随链接时,目标资源不能保证以指示的媒体类型或语言提供。
JSON:API 对象
JSON:API 文档**可以**在顶级 jsonapi
成员下包含有关其实现的信息。如果存在,jsonapi
成员的值**必须**是一个对象(“jsonapi 对象”)。
jsonapi 对象**可以**包含以下任何成员
version
- 其值为字符串,指示支持的最高 JSON:API 版本。ext
- 所有应用的扩展的 URI 数组。profile
- 所有应用的配置文件的 URI 数组。meta
- 一个元数据对象,包含非标准元信息。
客户端和服务器**不得**使用 ext
或 profile
成员进行内容协商。内容协商**必须**仅基于 Content-Type
标头中的媒体类型参数进行。
下面是一个简单的示例
{
"jsonapi": {
"version": "1.1",
"ext": [
"https://jsonapi.fullstack.org.cn/ext/atomic"
],
"profile": [
"http://example.com/profiles/flexible-pagination",
"http://example.com/profiles/resource-versioning"
]
}
}
如果 version
成员不存在,客户端应假定服务器至少实现了规范的 1.0 版本。
注意:由于 JSON:API 致力于仅进行增量更改,因此版本字符串主要指示服务器可能支持哪些新功能。
成员名称
客户端和服务器**必须**将 JSON:API 文档中使用的实现和配置文件定义的成员名称视为区分大小写,并且它们**必须**满足以下所有条件
- 成员名称**必须**包含至少一个字符。
- 成员名称**必须**仅包含下面列出的允许字符。
- 成员名称**必须**以“全局允许字符”开头和结尾,定义如下。
为了能够轻松地将成员名称映射到 URL,**建议**成员名称仅使用RFC 3986 中指定的非保留、URL 安全字符。
允许的字符
以下“全局允许字符”**可以**在成员名称的任何位置使用
- U+0061 到 U+007A,“a-z”
- U+0041 到 U+005A,“A-Z”
- U+0030 到 U+0039,“0-9”
- U+0080 及以上(非 ASCII Unicode 字符;*不建议,不安全*)
此外,以下字符在成员名称中允许使用,但不能作为第一个或最后一个字符
- U+002D HYPHEN-MINUS,“-”
- U+005F LOW LINE,“_”
- U+0020 SPACE,“ ” *(不建议,不安全)*
保留字符
以下字符**不得**在实现和配置文件定义的成员名称中使用
- U+002B PLUS SIGN,“+” *(在 URL 查询字符串中具有重载含义)*
- U+002C COMMA,“,” *(用作关系路径之间的分隔符)*
- U+002E PERIOD,“.” *(用作关系路径内的分隔符)*
- U+005B LEFT SQUARE BRACKET,“[” *(用在查询参数族中)*
- U+005D RIGHT SQUARE BRACKET,“]” *(用在查询参数族中)*
- U+0021 EXCLAMATION MARK,“!”
- U+0022 QUOTATION MARK,“”
- U+0023 NUMBER SIGN,“#”
- U+0024 DOLLAR SIGN,“$”
- U+0025 PERCENT SIGN,“%”
- U+0026 AMPERSAND,“&”
- U+0027 APOSTROPHE,“’”
- U+0028 LEFT PARENTHESIS,“(”
- U+0029 RIGHT PARENTHESIS,“)”
- U+002A ASTERISK,“*”
- U+002F SOLIDUS,“/”
- U+003A COLON,“:”
- U+003B SEMICOLON,“;”
- U+003C LESS-THAN SIGN,“<”
- U+003D EQUALS SIGN,“=”
- U+003E GREATER-THAN SIGN,“>”
- U+003F QUESTION MARK,“?”
- U+0040 COMMERCIAL AT,“@” (除非作为@-成员的第一个字符)
- U+005C REVERSE SOLIDUS,“\”
- U+005E CIRCUMFLEX ACCENT,“^”
- U+0060 GRAVE ACCENT,“`”
- U+007B LEFT CURLY BRACKET,“{“
- U+007C VERTICAL LINE,“|”
- U+007D RIGHT CURLY BRACKET,“}”
- U+007E TILDE,“~”
- U+007F DELETE
- U+0000 到 U+001F(C0 控制字符)
@-成员
成员名称**可以**以“@”符号(U+0040 COMMERCIAL AT,“@”)开头。以这种方式命名的成员称为“@-成员”。@-成员**可以**出现在文档中的任何位置。
本规范不提供有关 @-成员的含义或用法的指导,@-成员被认为是实现语义。在解释本规范的定义和处理指令时,**必须**忽略 @-成员,前提是这些指令在该小节之外给出。例如,属性在上面定义为属性对象的任何成员。但是,由于在解释该定义时必须忽略 @-成员,因此出现在属性对象中的 @-成员不是属性。
注意:“@”成员可以用于将 JSON-LD 数据添加到 JSON:API 文档中。此类文档应使用额外的标头提供,以传达给 JSON-LD 客户端,表明它们包含 JSON-LD 数据。
扩展成员
扩展引入的每个新成员的名称**必须**以扩展的命名空间后跟冒号 (:
) 为前缀。名称的其余部分**必须**遵守实现定义的成员名称的规则。
获取数据
数据(包括资源和关系)可以通过向端点发送 GET
请求来获取。
响应可以通过以下描述的可选功能进一步细化。
获取资源
服务器**必须**支持为作为以下内容提供的每个 URL 获取资源数据
- 作为顶级链接对象的一部分的
self
链接 - 作为资源级链接对象的一部分的
self
链接 - 作为关系级链接对象的一部分的
related
链接
例如,以下请求获取一组文章
GET /articles HTTP/1.1
Accept: application/vnd.api+json
以下请求获取一篇文章
GET /articles/1 HTTP/1.1
Accept: application/vnd.api+json
以下请求获取一篇文章的作者
GET /articles/1/author HTTP/1.1
Accept: application/vnd.api+json
响应
200 OK
服务器**必须**以 200 OK
响应来响应成功获取单个资源或资源集合的请求。
服务器**必须**以 资源对象 数组或空数组 ([]
) 作为响应文档的主要数据来响应成功获取资源集合的请求。
例如,对一组文章的 GET
请求可以返回
HTTP/1.1 200 OK
Content-Type: application/vnd.api+json
{
"links": {
"self": "http://example.com/articles"
},
"data": [{
"type": "articles",
"id": "1",
"attributes": {
"title": "JSON:API paints my bikeshed!"
}
}, {
"type": "articles",
"id": "2",
"attributes": {
"title": "Rails is Omakase"
}
}]
}
表示空集合的类似响应将是
HTTP/1.1 200 OK
Content-Type: application/vnd.api+json
{
"links": {
"self": "http://example.com/articles"
},
"data": []
}
服务器**必须**以 资源对象 或 null
作为响应文档的主要数据来响应成功获取单个资源的请求。
null
仅在请求的 URL 是可能对应于单个资源但当前不存在的 URL 时才适合作为响应。
注意:例如,考虑获取一对一相关资源链接的请求。当关系为空时(即链接不对应于任何资源),此请求将以
null
响应,但在其他情况下,将以单个相关资源的资源对象 响应。
例如,对单个文章的 GET
请求可以返回
HTTP/1.1 200 OK
Content-Type: application/vnd.api+json
{
"links": {
"self": "http://example.com/articles/1"
},
"data": {
"type": "articles",
"id": "1",
"attributes": {
"title": "JSON:API paints my bikeshed!"
},
"relationships": {
"author": {
"links": {
"related": "http://example.com/articles/1/author"
}
}
}
}
}
如果上面文章的作者丢失,那么对该相关资源的 GET
请求将返回
HTTP/1.1 200 OK
Content-Type: application/vnd.api+json
{
"links": {
"self": "http://example.com/articles/1/author"
},
"data": null
}
404 未找到
服务器**必须**以 404 未找到
响应来处理获取不存在的单个资源的请求,除非请求保证以 null
作为主要数据来返回 200 OK
响应(如上所述)。
其他响应
服务器**可以**以其他 HTTP 状态码响应。
服务器**可以**在错误响应中包含错误详细信息。
服务器**必须**根据HTTP 语义
准备响应,客户端**必须**根据该规范解释响应。
获取关系
服务器**必须**支持为作为关系的 links
对象的一部分的 self
链接提供的每个关系 URL 获取关系数据。
例如,以下请求获取有关文章评论的数据
GET /articles/1/relationships/comments HTTP/1.1
Accept: application/vnd.api+json
以下请求获取有关文章作者的数据
GET /articles/1/relationships/author HTTP/1.1
Accept: application/vnd.api+json
响应
200 OK
服务器**必须**以 200 OK
响应来响应成功获取关系的请求。
响应文档中的主要数据**必须**与资源链接的适当值匹配,如上面针对关系对象所述。
顶级链接对象**可以**包含 self
和 related
链接,如上面针对关系对象所述。
例如,对一对一关系链接的 URL 的 GET
请求可以返回
HTTP/1.1 200 OK
Content-Type: application/vnd.api+json
{
"links": {
"self": "/articles/1/relationships/author",
"related": "/articles/1/author"
},
"data": {
"type": "people",
"id": "12"
}
}
如果上面关系为空,那么对同一 URL 的 GET
请求将返回
HTTP/1.1 200 OK
Content-Type: application/vnd.api+json
{
"links": {
"self": "/articles/1/relationships/author",
"related": "/articles/1/author"
},
"data": null
}
对一对多关系链接的 URL 的 GET
请求可以返回
HTTP/1.1 200 OK
Content-Type: application/vnd.api+json
{
"links": {
"self": "/articles/1/relationships/tags",
"related": "/articles/1/tags"
},
"data": [
{ "type": "tags", "id": "2" },
{ "type": "tags", "id": "3" }
]
}
如果上面关系为空,那么对同一 URL 的 GET
请求将返回
HTTP/1.1 200 OK
Content-Type: application/vnd.api+json
{
"links": {
"self": "/articles/1/relationships/tags",
"related": "/articles/1/tags"
},
"data": []
}
404 未找到
服务器**必须**在处理获取不存在的关系链接 URL 的请求时返回 404 未找到
。
注意:当关系的父资源不存在时,可能会发生这种情况。例如,当
/articles/1
不存在时,对/articles/1/relationships/tags
的请求将返回404 未找到
。
如果关系链接 URL 存在但关系为空,那么**必须**返回 200 OK
,如上所述。
其他响应
服务器**可以**以其他 HTTP 状态码响应。
服务器**可以**在错误响应中包含错误详细信息。
服务器**必须**根据HTTP 语义
准备响应,客户端**必须**根据该规范解释响应。
包含相关资源
端点**可以**默认返回与主要数据相关的资源。
端点可以支持include
查询参数,允许客户端自定义应返回的关联资源。
如果端点不支持include
参数,它必须对包含该参数的任何请求返回400 Bad Request
。
如果端点支持include
参数,并且客户端提供它
include
参数的值必须是一个用逗号(U+002C COMMA,“,”)分隔的关联关系路径列表。关联关系路径是一个用点(U+002E FULL-STOP,“.”)分隔的关联关系名称列表。空值表示不应返回任何关联资源。
如果服务器无法识别关联关系路径,或者不支持从路径包含资源,它必须返回 400 Bad Request。
注意:例如,关联关系路径可以是
comments.author
,其中comments
是articles
资源对象下列出的关联关系,而author
是comments
资源对象下列出的关联关系。
例如,可以使用文章请求评论
GET /articles/1?include=comments HTTP/1.1
Accept: application/vnd.api+json
为了请求与其他资源相关的资源,可以为每个关联关系名称指定一个用点分隔的路径
GET /articles/1?include=comments.author HTTP/1.1
Accept: application/vnd.api+json
注意:由于复合文档需要完全关联(除非关联关系关联被稀疏字段集排除),因此多部分路径中的中间资源必须与叶子节点一起返回。例如,对
comments.author
的请求的响应应包含comments
以及每个comments
的author
。
注意:服务器可以选择将深度嵌套的关联关系(如
comments.author
)公开为具有备用名称(如commentAuthors
)的直接关联关系。这将允许客户端请求/articles/1?include=commentAuthors
,而不是/articles/1?include=comments.author
。通过使用备用名称公开嵌套的关联关系,服务器仍然可以在复合文档中提供完全关联,而不会包含可能不需要的中间资源。
可以在逗号分隔列表中请求多个关联资源
GET /articles/1?include=comments.author,ratings HTTP/1.1
Accept: application/vnd.api+json
此外,可以从关联关系端点请求关联资源
GET /articles/1/relationships/comments?include=comments.author HTTP/1.1
Accept: application/vnd.api+json
在这种情况下,主要数据将是一组资源标识符对象,这些对象表示对文章的评论的关联关系,而完整的评论和评论作者将作为包含的数据返回。
注意:本节适用于返回主要数据的任何端点,无论请求类型如何。例如,服务器可以支持在
POST
请求创建资源或关联关系时包含关联资源。
稀疏字段集
客户端可以请求端点在响应中仅返回特定字段,方式是在每个类型基础上包含fields[TYPE]
查询参数。
任何fields[TYPE]
参数的值必须是一个用逗号(U+002C COMMA,“,”)分隔的列表,该列表引用要返回的字段名称。空值表示不应返回任何字段。
如果客户端请求特定资源类型的受限字段集,则端点不得在其响应中包含该类型资源对象中的其他字段。
如果客户端未指定特定资源类型的字段集,则服务器可以为该资源类型发送所有字段、子集字段或无字段。
GET /articles?include=author&fields[articles]=title,body&fields[people]=name HTTP/1.1
Accept: application/vnd.api+json
注意:上面的示例 URI 显示未编码的
[
和]
字符,只是为了可读性。实际上,这些字符应该进行百分比编码。请参阅“参数名称中的方括号”。
注意:本节适用于返回资源作为主要数据或包含数据的任何端点,无论请求类型如何。例如,服务器可以支持稀疏字段集以及
POST
请求创建资源。
排序
服务器可以选择支持请求,以便根据一个或多个标准(“排序字段”)对资源集合进行排序。
注意:虽然建议这样做,但排序字段不一定需要与资源属性和关联关系名称相对应。
注意:建议使用点分隔(U+002E FULL-STOP,“.”)排序字段来请求基于关联关系属性的排序。例如,排序字段
author.name
可用于请求将主要数据根据author
关联关系的name
属性进行排序。
端点可以支持使用sort
查询参数对主要数据进行排序的请求。sort
的值必须表示排序字段。
GET /people?sort=age HTTP/1.1
Accept: application/vnd.api+json
端点可以通过允许用逗号(U+002C COMMA,“,”)分隔的排序字段来支持多个排序字段。应按指定顺序应用排序字段。
GET /people?sort=age,name HTTP/1.1
Accept: application/vnd.api+json
每个排序字段的排序顺序必须为升序,除非以减号(U+002D HYPHEN-MINUS,“-”)为前缀,在这种情况下,它必须为降序。
GET /articles?sort=-created,title HTTP/1.1
Accept: application/vnd.api+json
上面的示例应该首先返回最新的文章。在同一天创建的任何文章将按标题的升序字母顺序排序。
如果服务器不支持查询参数sort
中指定的排序,它必须返回400 Bad Request
。
如果服务器支持排序,并且客户端通过查询参数sort
请求排序,则服务器必须根据指定的标准对响应的顶级data
数组的元素进行排序。如果未指定请求参数sort
,服务器可以对顶级data
应用默认排序规则。
注意:本节适用于返回资源集合作为主要数据的任何端点,无论请求类型如何。
分页
服务器可以选择将响应中返回的资源数量限制为可用完整集合的子集(“页面”)。
服务器可以提供用于遍历分页数据集(“分页链接”)的链接。
分页链接必须出现在与集合对应的links对象中。要对主要数据进行分页,请在顶级links
对象中提供分页链接。要对复合文档中返回的包含集合进行分页,请在相应的links对象中提供分页链接。
以下键必须用于分页链接
first
:第一页数据last
:最后一页数据prev
:上一页数据next
:下一页数据
键必须被省略或具有null
值,以指示特定链接不可用。
如分页链接命名中所表达的顺序概念必须与 JSON:API 的排序规则保持一致。
page
查询参数族保留用于分页。服务器和客户端应使用这些参数进行分页操作。
注意:JSON API 对服务器使用的分页策略保持中立,但无论采用何种策略,都可使用
page
查询参数族。例如,基于页面的策略可能使用page[number]
和page[size]
等查询参数,而基于游标的策略可能使用page[cursor]
。
注意:本节适用于返回资源集合作为主要数据的任何端点,无论请求类型如何。
过滤
filter
查询参数族保留用于过滤数据。服务器和客户端应使用这些参数进行过滤操作。
注意:JSON API 对服务器支持的策略保持中立。
创建、更新和删除资源
服务器可以允许创建特定类型的资源。它也可以允许修改或删除现有资源。
请求必须完全成功或失败(在一个“事务”中)。不允许部分更新。
注意:
type
成员在 JSON:API 中的请求和响应中的每个资源对象中都是必需的。在某些情况下,例如当POST
到表示异构数据的端点时,type
无法从端点推断出来。但是,选择何时需要它会令人困惑;很难记住何时需要它,何时不需要它。因此,为了提高一致性并最大限度地减少混淆,type
始终是必需的。
创建资源
可以通过向代表资源集合的 URL 发送POST
请求来创建资源。请求必须包含单个资源对象作为主要数据。资源对象必须至少包含type
成员。
例如,可以使用以下请求创建一张新照片
POST /photos HTTP/1.1
Content-Type: application/vnd.api+json
Accept: application/vnd.api+json
{
"data": {
"type": "photos",
"attributes": {
"title": "Ember Hamster",
"src": "http://example.com/images/productivity.png"
},
"relationships": {
"photographer": {
"data": { "type": "people", "id": "9" }
}
}
}
}
如果在资源对象的relationships
成员中提供了关联关系,则其值必须是包含data
成员的关联关系对象。此键的值表示新资源将具有的关联关系。
客户端生成的 ID
服务器**可以**接受客户端生成的 ID,以及创建资源的请求。ID**必须**使用 `id` 键指定,其值**必须**是全局唯一的标识符。客户端**应该**使用 RFC 4122 中描述的正确生成和格式化的 UUID [RFC4122]。
注意:在某些用例中,例如从其他来源导入数据,可以使用 UUID 之外的其他内容,但仍能保证全局唯一性。除非您 100% 确信您使用的策略确实生成了全局唯一的标识符,否则不要使用 UUID 之外的任何其他内容。
例如
POST /photos HTTP/1.1
Content-Type: application/vnd.api+json
Accept: application/vnd.api+json
{
"data": {
"type": "photos",
"id": "550e8400-e29b-41d4-a716-446655440000",
"attributes": {
"title": "Ember Hamster",
"src": "http://example.com/images/productivity.png"
}
}
}
服务器**必须**返回 `403 Forbidden`,以响应不支持的创建带有客户端生成 ID 的资源的请求。
响应
201 Created
如果请求的资源已成功创建,并且服务器以任何方式更改了资源(例如,通过分配 `id`),则服务器**必须**返回 `201 Created` 响应以及包含资源作为主要数据的文档。
为了符合 RFC 7231,响应**应该**包含一个 `Location` 头,用于标识新创建资源的位置。
如果响应返回的 资源对象 在其 `links` 成员中包含 `self` 键,并且提供了 `Location` 头,则 `self` 成员的值**必须**与 `Location` 头的值匹配。
HTTP/1.1 201 Created
Location: http://example.com/photos/550e8400-e29b-41d4-a716-446655440000
Content-Type: application/vnd.api+json
{
"data": {
"type": "photos",
"id": "550e8400-e29b-41d4-a716-446655440000",
"attributes": {
"title": "Ember Hamster",
"src": "http://example.com/images/productivity.png"
},
"links": {
"self": "http://example.com/photos/550e8400-e29b-41d4-a716-446655440000"
}
}
}
如果请求的资源已成功创建,并且服务器未以任何方式更改资源(例如,通过分配 `id` 或 `createdAt` 属性),则服务器**可以**返回包含 `201 Created` 响应以及包含无主要数据的文档。其他顶级成员,例如 meta,可以包含在响应文档中。
注意:只有接受 客户端生成的 ID 的服务器才能避免为新资源分配 `id`。
202 Accepted
如果创建资源的请求已被接受处理,但处理在服务器响应时尚未完成,则服务器**必须**返回 `202 Accepted` 状态码。
204 No Content
如果请求的资源已成功创建,并且服务器未以任何方式更改资源(例如,通过分配 `id` 或 `createdAt` 属性),则服务器**必须**返回 `201 Created` 状态码和响应文档(如上所述),或者返回 `204 No Content` 状态码和无响应文档。
403 Forbidden
服务器**可以**返回 `403 Forbidden`,以响应不支持的创建资源的请求。
404 Not Found
服务器**必须**在处理引用不存在的相关资源的请求时返回 `404 Not Found`。
409 Conflict
服务器**必须**在处理 `POST` 请求以创建带有已存在客户端生成 ID 的资源时返回 `409 Conflict`。
服务器**必须**在处理 `POST` 请求时返回 `409 Conflict`,其中 资源对象 的 `type` 不在构成由端点表示的集合的类型中。
服务器**应该**包含错误详细信息,并提供足够的信息来识别冲突的来源。
其他响应
服务器**可以**以其他 HTTP 状态码响应。
服务器**可以**在错误响应中包含错误详细信息。
服务器**必须**根据HTTP 语义
准备响应,客户端**必须**根据该规范解释响应。
更新资源
可以通过向表示资源的 URL 发送 `PATCH` 请求来更新资源。
资源的 URL 可以从资源对象的 `self` 链接中获得。或者,当 `GET` 请求返回单个 资源对象 作为主要数据时,可以使用相同的请求 URL 进行更新。
`PATCH` 请求**必须**包含单个 资源对象 作为主要数据。 资源对象 **必须**包含 `type` 和 `id` 成员。
例如
PATCH /articles/1 HTTP/1.1
Content-Type: application/vnd.api+json
Accept: application/vnd.api+json
{
"data": {
"type": "articles",
"id": "1",
"attributes": {
"title": "To TDD or Not"
}
}
}
更新资源的属性
资源的任何或所有 属性 **可以**包含在 `PATCH` 请求中包含的资源对象中。
如果请求不包含资源的所有 属性,则服务器**必须**将缺少的 属性 解释为它们包含其当前值。服务器**不得**将缺少的属性解释为 `null` 值。
例如,以下 `PATCH` 请求被解释为仅更新文章的 `title` 和 `text` 属性的请求
PATCH /articles/1 HTTP/1.1
Content-Type: application/vnd.api+json
Accept: application/vnd.api+json
{
"data": {
"type": "articles",
"id": "1",
"attributes": {
"title": "To TDD or Not",
"text": "TLDR; It's complicated... but check your test coverage regardless."
}
}
}
更新资源的关系
资源的任何或所有 关系 **可以**包含在 `PATCH` 请求中包含的资源对象中。
如果请求不包含资源的所有 关系,则服务器**必须**将缺少的 关系 解释为它们包含其当前值。它**不得**将它们解释为 `null` 或空值。
如果在 `PATCH` 请求的资源对象的 `relationships` 成员中提供了关系,则其值**必须**是包含 `data` 成员的关系对象。关系的值将被替换为此成员中指定的值。
例如,以下 `PATCH` 请求将更新文章的 `author` 关系
PATCH /articles/1 HTTP/1.1
Content-Type: application/vnd.api+json
Accept: application/vnd.api+json
{
"data": {
"type": "articles",
"id": "1",
"relationships": {
"author": {
"data": { "type": "people", "id": "1" }
}
}
}
}
同样,以下 `PATCH` 请求将完全替换文章的 `tags`
PATCH /articles/1 HTTP/1.1
Content-Type: application/vnd.api+json
Accept: application/vnd.api+json
{
"data": {
"type": "articles",
"id": "1",
"relationships": {
"tags": {
"data": [
{ "type": "tags", "id": "2" },
{ "type": "tags", "id": "3" }
]
}
}
}
}
服务器**可以**拒绝尝试完全替换多对多关系。在这种情况下,服务器**必须**拒绝整个更新,并返回 `403 Forbidden` 响应。
注意:由于完全替换可能是一项非常危险的操作,因此服务器可以选择禁止它。例如,如果服务器未向客户端提供关联对象的完整列表,并且不想允许删除客户端未曾见过的记录,则服务器可能会拒绝完全替换。
响应
200 OK
如果服务器接受更新,但还以请求中未指定的方式更改了目标资源(例如,更新 `updatedAt` 属性或计算出的 `sha`),则它**必须**返回 `200 OK` 响应以及包含更新后的资源作为主要数据的文档。
如果更新成功,并且服务器未以请求中未指定的方式更改目标资源,则服务器**可以**返回包含 `200 OK` 响应以及包含无主要数据的文档。其他顶级成员,例如 meta,可以包含在响应文档中。
202 Accepted
如果更新请求已被接受处理,但处理在服务器响应时尚未完成,则服务器**必须**返回 `202 Accepted` 状态码。
204 No Content
如果更新成功,并且服务器未以请求中未指定的方式更改目标资源,则服务器**必须**返回 `200 OK` 状态码和响应文档(如上所述),或者返回 `204 No Content` 状态码和无响应文档。
403 Forbidden
服务器**必须**返回 `403 Forbidden`,以响应不支持的更新资源或关系的请求。
404 Not Found
服务器**必须**在处理修改不存在的资源的请求时返回 `404 Not Found`。
服务器**必须**在处理引用不存在的相关资源的请求时返回 `404 Not Found`。
409 Conflict
服务器**可以**在处理 `PATCH` 请求以更新资源时返回 `409 Conflict`,如果该更新会违反其他服务器强制执行的约束(例如,对 `id` 以外的属性的唯一性约束)。
服务器**必须**在处理 `PATCH` 请求时返回 `409 Conflict`,其中资源对象的 `type` 或 `id` 与服务器的端点不匹配。
服务器**应该**包含错误详细信息,并提供足够的信息来识别冲突的来源。
其他响应
服务器**可以**以其他 HTTP 状态码响应。
服务器**可以**在错误响应中包含错误详细信息。
服务器**必须**根据HTTP 语义
准备响应,客户端**必须**根据该规范解释响应。
更新关系
虽然关系可以在与资源一起修改时进行修改(如上所述),但 JSON:API 还支持通过从 关系链接 中的 URL 独立更新关系。
注意:更新关系时不会暴露底层服务器语义,例如外键。此外,更新关系时并不一定需要影响相关资源。例如,如果一篇文章有多位作者,可以从文章中删除其中一位作者,而不会删除作者本人。类似地,如果一篇文章有多个标签,则可以添加或删除标签。在服务器内部,第一个示例可能使用外键实现,而第二个示例可能使用联接表实现,但 JSON:API 协议在这两种情况下都相同。
注意:如果删除了关系,服务器可以选择删除底层资源(作为垃圾回收措施)。
更新一对一关系
可以通过向一对一 关系链接 中的 URL 发送 PATCH
请求来更新一对一关系。
PATCH
请求 **必须** 包含一个名为 data
的顶层成员,其中包含以下内容:
- 一个与新关联资源相对应的 资源标识符对象。
null
,用于删除关系。
例如,以下请求更新文章的作者
PATCH /articles/1/relationships/author HTTP/1.1
Content-Type: application/vnd.api+json
Accept: application/vnd.api+json
{
"data": { "type": "people", "id": "12" }
}
以下请求清除同一文章的作者
PATCH /articles/1/relationships/author HTTP/1.1
Content-Type: application/vnd.api+json
Accept: application/vnd.api+json
{
"data": null
}
如果关系更新成功,则服务器 **必须** 返回成功响应。
更新一对多关系
可以通过向一对多 关系链接 中的 URL 发送 PATCH
、POST
或 DELETE
请求来更新一对多关系。
对于所有请求类型,请求体 **必须** 包含一个 data
成员,其值为一个空数组或一个 资源标识符对象 数组。
如果客户端向一对多 关系链接 中的 URL 发送 PATCH
请求,则服务器 **必须** 既完全替换关系中的每个成员,又返回一个合适的错误响应(如果某些资源无法找到或访问),或者返回一个 403 Forbidden
响应(如果服务器不允许完全替换)。
例如,以下请求替换文章的所有标签
PATCH /articles/1/relationships/tags HTTP/1.1
Content-Type: application/vnd.api+json
Accept: application/vnd.api+json
{
"data": [
{ "type": "tags", "id": "2" },
{ "type": "tags", "id": "3" }
]
}
以下请求清除文章的所有标签
PATCH /articles/1/relationships/tags HTTP/1.1
Content-Type: application/vnd.api+json
Accept: application/vnd.api+json
{
"data": []
}
如果客户端向 关系链接 中的 URL 发送 POST
请求,则服务器 **必须** 将指定的成员添加到关系中(除非它们已经存在)。如果给定的 type
和 id
已经在关系中,服务器 **不应** 再次添加它。
注意:这与使用外键进行一对多关系的数据库语义相匹配。文档型存储应在追加之前检查一对多关系,以避免重复。
如果所有指定的资源都可以添加到关系中或已经存在于关系中,则服务器 **必须** 返回成功响应。
注意:这种方法确保如果服务器状态与请求状态匹配,则请求将成功,并有助于避免多个客户端对关系进行相同更改而导致的无意义的竞态条件。
在以下示例中,ID 为 123
的评论被添加到 ID 为 1
的文章的评论列表中
POST /articles/1/relationships/comments HTTP/1.1
Content-Type: application/vnd.api+json
Accept: application/vnd.api+json
{
"data": [
{ "type": "comments", "id": "123" }
]
}
如果客户端向 关系链接 中的 URL 发送 DELETE
请求,则服务器 **必须** 从关系中删除指定的成员,或返回一个 403 Forbidden
响应。如果所有指定的资源都可以从关系中删除或已经从关系中消失,则服务器 **必须** 返回成功响应。
注意:如上所述,对于
POST
请求,这种方法有助于避免多个客户端进行相同更改而导致的无意义的竞态条件。
关系成员的指定方式与 POST
请求中的方式相同。
在以下示例中,ID 为 12
和 13
的评论被从 ID 为 1
的文章的评论列表中删除
DELETE /articles/1/relationships/comments HTTP/1.1
Content-Type: application/vnd.api+json
Accept: application/vnd.api+json
{
"data": [
{ "type": "comments", "id": "12" },
{ "type": "comments", "id": "13" }
]
}
注意:RFC 7231 指出 DELETE 请求可以包含请求体,但服务器可以拒绝请求。本规范定义了服务器的语义,我们正在定义其对 JSON:API 的语义。
响应
200 OK
如果服务器接受更新,但也以请求中未指定的方式更改目标关系,则它 **必须** 返回一个 200 OK
响应和一个包含更新后的关系数据作为其主要数据的文档。
如果更新成功并且服务器没有以请求中未指定的方式更改目标关系,则服务器 **可以** 返回一个 200 OK
响应,其中包含一个没有主要数据的文档。响应文档中可以包含其他顶层成员,例如 meta。
202 Accepted
如果关系更新请求已被接受处理,但在服务器响应时处理尚未完成,则服务器 **必须** 返回 202 Accepted
状态代码。
204 No Content
如果更新成功并且服务器没有以请求中未指定的方式更改目标关系,则服务器 **必须** 返回 200 OK
状态代码和响应文档(如上所述),或者返回 204 No Content
状态代码,没有响应文档。
注意:这是对发送到一对多 关系链接 中的 URL 的
POST
请求的适当响应,当该关系已经存在时。它也是对发送到一对多 关系链接 中的 URL 的DELETE
请求的适当响应,当该关系不存在时。
403 Forbidden
服务器 **必须** 返回 403 Forbidden
以响应对更新关系的非支持请求。
其他响应
服务器**可以**以其他 HTTP 状态码响应。
服务器**可以**在错误响应中包含错误详细信息。
服务器**必须**根据HTTP 语义
准备响应,客户端**必须**根据该规范解释响应。
删除资源
可以通过向表示资源的 URL 发送 DELETE
请求来删除资源
DELETE /photos/1 HTTP/1.1
Accept: application/vnd.api+json
响应
200 OK
如果删除请求成功,服务器 **可以** 返回一个 200 OK
响应,其中包含一个没有主要数据的文档。响应文档中可以包含其他顶层成员,例如 meta。
202 Accepted
如果删除请求已被接受处理,但在服务器响应时处理尚未完成,则服务器 **必须** 返回 202 Accepted
状态代码。
204 No Content
如果删除请求成功,则服务器 **必须** 返回 200 OK
状态代码和响应文档(如上所述),或者返回 204 No Content
状态代码,没有响应文档。
404 NOT FOUND
如果删除请求由于资源不存在而失败,则服务器 **应** 返回 404 Not Found
状态代码。
其他响应
服务器**可以**以其他 HTTP 状态码响应。
服务器**可以**在错误响应中包含错误详细信息。
服务器**必须**根据HTTP 语义
准备响应,客户端**必须**根据该规范解释响应。
查询参数
查询参数族
虽然“查询参数”在日常 Web 开发中是一个常用术语,但它不是一个标准化的概念。因此,JSON:API 提供了自己的 查询参数定义。
在大多数情况下,JSON:API 的定义与口语用法一致,其细节可以安全地忽略。然而,此定义的一个重要结果是,像以下这样的 URL 被认为具有两个不同的查询参数
/?page[offset]=0&page[limit]=10
这两个参数分别名为 page[offset]
和 page[limit]
;没有单个 page
参数。
然而,在实践中,像 page[offset]
和 page[limit]
这样的参数通常是定义和一起处理的,将它们统称为一个概念很方便。因此,JSON:API 引入了查询参数族的概念。
“查询参数族”是指所有查询参数的集合,这些参数的名称都以“基本名称”开头,后面跟着零个或多个空方括号(即 []
)、方括号括起来的合法 成员名称 或方括号括起来的点分隔的合法成员名称列表。这个族由其基本名称来引用。
例如,filter
查询参数族包含以下参数:filter
、filter[x]
、filter[]
、filter[x][]
、filter[][]
、filter[x][y]
、filter[x.y]
等等。但是,filter[_]
不是族中有效的参数名称,因为 _
不是有效的 成员名称。
注意:点分隔的合法成员名称列表用于关系路径。例如,这允许使用关系路径进行过滤策略,如 [排序][fetching sorting] 中定义的查询参数,例如
GET /posts?sort=author.name&filter[author.status]=active
。
扩展特定的查询参数
扩展引入的每个查询参数的基本名称 **必须** 以扩展的命名空间后跟冒号 (:
) 为前缀。基本名称的其余部分 **必须** 只包含字符 [a-z] (U+0061 到 U+007A,即“a-z”)。
实现特定的查询参数
实现 **可以** 支持自定义查询参数。但是,这些查询参数的名称 **必须** 来自一个 族,该族的基本名称是一个合法的 成员名称,并且至少包含一个非 a-z 字符(即,不在 U+0061 到 U+007A 之间)。
建议使用首字母大写(例如 camelCasing)来满足上述要求。
如果服务器遇到不符合上述命名规范的查询参数,或者服务器不知道如何根据本规范处理该查询参数,它 **必须** 返回 400 Bad Request
。
注意:通过禁止使用仅包含字符 [a-z] 的查询参数,JSON:API 保留了在未来标准化更多查询参数的能力,而不会与现有实现冲突。
错误
处理错误
服务器 **可以** 选择在遇到问题时停止处理,或者 **可以** 继续处理并遇到多个问题。例如,服务器可能会处理多个属性,然后在单个响应中返回多个验证问题。
当服务器针对单个请求遇到多个问题时,响应中 **应该** 使用最通用的 HTTP 错误代码。例如,对于多个 4xx 错误,400 Bad Request
可能适用;对于多个 5xx 错误,500 Internal Server Error
可能适用。
错误对象
错误对象提供有关在执行操作时遇到的问题的更多信息。错误对象 **必须** 作为以 errors
为键的数组返回,位于 JSON:API 文档的顶层。
错误对象 **可以** 包含以下成员,并且 **必须** 包含至少一个以下成员:
id
: 此特定问题发生的唯一标识符。links
: 一个 链接对象,**可以** 包含以下成员:status
: 适用于此问题的 HTTP 状态代码,以字符串值表示。**应该** 提供此信息。code
: 一个特定于应用程序的错误代码,以字符串值表示。title
: 对该问题的简短、人类可读的摘要,**不应该** 在该问题发生时发生变化,除非为了本地化目的。detail
: 对该问题发生的特定人类可读解释。与title
一样,此字段的值可以本地化。source
: 一个包含对错误主要来源引用的对象。它 **应该** 包含以下成员之一或被省略:pointer
: 一个指向请求文档中导致错误的值的 JSON 指针 [RFC6901] [例如,对于主要数据对象,"/data"
;对于特定属性,"/data/attributes/title"
]。此指针 **必须** 指向请求文档中存在的值;如果不存在,客户端 **应该** 忽略该指针。parameter
: 一个字符串,指示哪个 URI 查询参数导致了错误。header
: 一个字符串,指示导致错误的单个请求头的名称。
meta
: 一个 元数据对象,包含有关该错误的非标准元数据信息。
附录
查询参数详细信息
解析/序列化
查询参数是从 URI 的查询字符串中提取或序列化成 URI 的查询字符串的名称-值对。
要从 URI 中提取查询参数,实现 **必须** 将 URI 的查询字符串(不包括前导问号)通过 application/x-www-form-urlencoded
解析算法 进行处理,但有一个例外:JSON:API 允许定义查询参数使用方式的规范提供自己的规则,用于从 application/x-www-form-urlencoded
解析算法的步骤 3.2 和 3.3 中标识的 value
字节中解析参数的值。最终值可能不是字符串。
注意:通常,服务器和浏览器中内置的查询字符串解析与上面指定的流程相匹配,因此大多数实现不需要担心这个问题。
引用
application/x-www-form-urlencoded
格式,因为它几乎所有查询字符串今天使用的a=b&c=d
样式的基础。但是,
application/x-www-form-urlencoded
解析包含奇怪的历史遗留问题,即+
字符必须被视为空格,并且它要求在解析期间对所有值进行百分比解码,这使得不可能使用 RFC 3986 分隔符 作为分隔符。这些问题促使了 JSON:API 在上面定义的例外。
类似地,要将查询参数序列化成 URI,实现 **必须** 使用 the application/x-www-form-urlencoded
序列化器,并具有相应的例外,即参数的值(但不是名称)可以以不同于该算法要求的方式进行序列化,前提是序列化不会干扰反向解析结果 URI 的能力。
参数名称中的方括号
对于 查询参数族,JSON:API 允许查询参数的名称包含方括号(即,U+005B “[” 和 U+005D “]”)。
根据上面的查询参数序列化规则,符合规范的实现将对这些方括号进行百分比编码。但是,一些 URI 生产者(即浏览器)并不总是对其进行编码。服务器 **应该** 接受请求,其中这些方括号在查询参数的名称中未被编码。如果服务器接受这些请求,它 **必须** 将该请求视为等效于方括号被百分比编码的请求。