规范 v1.1(存档副本)
状态
此页面展示了 JSON:API 版本 1.1 的存档副本。本页面的规范文本将不会更改。后续版本的 JSON:API 将保持与本版本兼容,因为 JSON:API 使用的是从不删除,只添加的策略。
如果您发现规范文本中的错误,或者您编写了实现,请通过在我们的 GitHub 仓库 中创建 issue 或 pull request 告知我们。
简介
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"
}
}
}
// ...
内容协商
通用责任
客户端和服务器必须使用 Content-Type 标头中的 JSON:API 媒体类型发送所有 JSON:API 有效负载。
客户端和服务器必须在将一个或多个扩展应用于 JSON:API 文档时,在 Content-Type 标头中指定 ext 媒体类型参数。
客户端和服务器必须在将一个或多个概要文件应用于 JSON:API 文档时,在 Content-Type 标头中指定 profile 媒体类型参数。
客户端责任
在处理 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 标头中指定了由 ext 媒体类型参数修改的 JSON:API 媒体类型的实例,并且该参数包含不支持的扩展 URI,则服务器必须以 415 Unsupported Media Type 状态代码进行响应。
注意:不支持本规范版本 1.1 的 JSON:API 服务器,如果
ext或profile媒体类型参数存在,将以415 Unsupported Media Type客户端错误进行响应。
如果请求的 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:API 请求和响应文档的根部 **必须** 是一个 JSON 对象。此对象定义了文档的“最顶层”。
文档 **必须** 包含以下至少一个最顶层成员
成员 data 和 errors **不得** 在同一个文档中同时存在。
文档 **可以** 包含以下任何最顶层成员
如果文档不包含最顶层的 data 键,则 included 成员 **不得** 存在。
最顶层的 links 对象 **可以** 包含以下成员
self:生成当前响应文档的 链接。如果文档应用了扩展或配置文件,则此链接 **应该** 由一个 link 对象 表示,其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 文档中,用于表示资源。
资源对象 **必须** 包含以下至少一个最顶层成员
idtype
例外:当资源对象起源于客户端并表示要创建到服务器上的新资源时,id 成员不是必需的。在这种情况下,客户端 **可以** 包含 lid 成员,以便在文档中按 type 本地 唯一地标识资源。
此外,资源对象 **可以** 包含以下任何最顶层成员
attributes:一个 attributes 对象,表示资源数据的一部分。relationships:一个 relationships 对象,描述资源与其他 JSON:API 资源之间的关系。links:一个 links 对象,包含与资源相关的链接。meta:一个 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的值可以是复数或单数。但是,在整个实现中应始终使用相同的值。
字段
资源对象的 attributes 及其 relationships 统称为“字段”。
资源对象 的字段 **必须** 与彼此以及与 type 和 id 共享一个公共命名空间。换句话说,资源不能具有相同名称的属性和关系,也不能具有名为 type 或 id 的属性或关系。
属性
attributes 键的值 **必须** 是一个对象(“attributes 对象”)。attributes 对象的成员(“属性”)表示有关定义它的 资源对象 的信息。
属性可以包含任何有效的 JSON 值,包括涉及 JSON 对象和数组的复杂数据结构。
引用相关资源的键(例如 author_id)**不应**作为属性出现。相反,**应**使用 关系。
关系
relationships 键的值**必须**是一个对象(“关系对象”)。关系对象的每个成员表示从定义它的 资源对象 到其他资源对象的“关系”。
关系可以是一对一或一对多。
关系的名称由其键给出。该键处的值**必须**是一个对象(“关系对象”)。
表示一对多关系的关系对象**可以**在 links 成员下包含 分页 链接,如下所述。关系对象中的任何 分页 链接**必须**对关系数据进行分页,而不是对相关资源进行分页。
相关资源链接
“相关资源链接”提供对 资源对象 的访问权限,这些对象 链接 到 关系 中。当获取时,相关资源对象将作为响应的主要数据返回。
例如,article 的 comments 关系 可以指定一个 链接,当通过 GET 请求检索时,该链接将返回评论 资源对象 的集合。
如果存在,相关资源链接**必须**引用有效的 URL,即使关系当前未与任何目标资源相关联。此外,相关资源链接**不应**因关系内容的更改而更改。
资源链接
在 复合文档 中的资源链接允许客户端将所有包含的 资源对象 链接在一起,而无需通过 链接 GET 任何 URL。
资源链接**必须**以以下一种形式表示
- 对于空的“一对一”关系,为
null。 - 对于空的“一对多”关系,为一个空数组 (
[])。 - 对于非空的“一对一”关系,为一个单独的 资源标识符对象。
- 对于非空的“一对多”关系,为一个 资源标识符对象 数组。
注意:规范不赋予“一对多”关系链接数组中资源标识符对象的顺序任何含义,尽管实现可以这样做。资源标识符对象数组可以表示有序或无序关系,并且两种类型可以在一个响应对象中混合使用。
例如,以下文章与一个 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 关系包含一个用于关系本身的链接(允许客户端直接更改相关的作者)、一个用于获取资源对象的相关资源链接以及链接信息。
资源链接
每个 资源对象 中的可选 links 成员包含与资源相关的 链接。
如果存在,此链接对象**可以**包含一个 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
}
}
}
链接对象
“链接对象”是一个表示 Web 链接 的对象。
链接对象**必须**包含以下成员
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 连字符,“-”
- U+005F 下划线,“_”
- U+0020 空格,“ ” (不推荐,不安全)
保留字符
以下字符**不得**在实现和配置文件定义的成员名称中使用
- U+002B 加号,“+” (在 URL 查询字符串中具有重载含义)
- U+002C 逗号,“,” (用作关系路径之间的分隔符)
- U+002E 句点,“.” (用作关系路径中的分隔符)
- U+005B 左方括号,“[” (用于查询参数族)
- U+005D 右方括号,“]” (用于查询参数族)
- U+0021 感叹号,“!”
- U+0022 双引号,“’”
- U+0023 井号,“#”
- U+0024 美元符号,“$”
- U+0025 百分号,“%”
- U+0026 与号,“&”
- U+0027 单引号,“’”
- U+0028 左括号,“(“
- U+0029 右括号,“)”
- U+002A 星号,“*”
- U+002F 斜杠,“/”
- U+003A 冒号,“:”
- U+003B 分号,“;”
- U+003C 小于号,“<”
- U+003D 等号,“=”
- U+003E 大于号,“>”
- U+003F 问号,“?”
- U+0040 商业符号,“@” (除了在@-Members中作为第一个字符)
- U+005C 反斜杠,“\”
- U+005E 插入符号,“^”
- U+0060 重音符,“`”
- U+007B 左大括号,“{“
- U+007C 竖线,“|”
- U+007D 右大括号,“}”
- U+007E 波浪号,“~”
- U+007F 删除
- U+0000 到 U+001F(C0 控制字符)
@-Members
成员名称**也可以**以一个 at 符号 (U+0040 商业符号,“@”) 开头。以这种方式命名的成员称为“@-Members”。@-Members**可以**出现在文档中的任何位置。
本规范没有提供关于 @-Members 的含义或用法的指导,它们被认为是实现语义。@-Members**必须**在解释本规范的定义和处理指令时被忽略,这些定义和处理指令在本章节之外给出。例如,上面将属性定义为属性对象的任何成员。但是,由于在解释该定义时必须忽略 @-Members,因此出现在属性对象中的 @-Member 不是属性。
注意:“@” 成员可以用于将 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 Not Found
服务器**必须**在处理请求获取不存在的单个资源时以404 Not Found进行响应,除非请求保证以null作为主数据返回200 OK响应(如上所述)。
其他响应
服务器**可以**以其他 HTTP 状态码进行响应。
服务器**可以**在错误响应中包含错误详细信息。
服务器**必须**根据HTTP 语义准备响应,客户端**必须**根据该语义解释响应。
获取关系
服务器**必须**支持获取每个作为关系的links对象的一部分提供的self链接的关系数据。
例如,以下请求获取有关一篇文章的评论的数据
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 Not Found
服务器**必须**在处理请求获取不存在的关系链接 URL 时返回404 Not Found。
注意:当关系的父资源不存在时,就会发生这种情况。例如,当
/articles/1不存在时,对/articles/1/relationships/tags的请求将返回404 Not Found。
如果关系链接 URL 存在,但关系为空,那么**必须**返回200 OK,如上所述。
其他响应
服务器**可以**以其他 HTTP 状态码进行响应。
服务器**可以**在错误响应中包含错误详细信息。
服务器**必须**根据HTTP 语义准备响应,客户端**必须**根据该语义解释响应。
包含相关资源
端点**可以**默认返回与主数据相关联的资源。
端点**也可以**支持include查询参数,允许客户端自定义应返回哪些相关资源。
如果一个端点不支持 include 参数,它 **必须** 对包含该参数的任何请求返回 400 Bad Request。
如果一个端点支持 include 参数,并且客户端提供它
- 服务器的响应 **必须** 是一个带有
included键的 复合文档 - 即使这个included键包含一个空数组(因为请求的关系为空)。 - 服务器 **不得** 在 复合文档 的
included部分中包含未请求的 资源对象。
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 [RFC4122] 中描述的正确生成和格式化的 UUID。
注意:在某些用例中,例如从其他来源导入数据,可以使用 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"
}
}
}
服务器**必须**在响应不支持的创建具有客户端生成 ID 的资源的请求时返回403 Forbidden。
响应
201 已创建
如果请求的资源已成功创建,并且服务器以任何方式更改了资源(例如,通过分配一个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 已接受
如果创建资源的请求已被接受处理,但处理在服务器响应之前尚未完成,服务器**必须**返回202 Accepted状态代码。
204 无内容
如果请求的资源已成功创建,并且服务器没有以任何方式更改资源(例如,通过分配一个id或createdAt属性),服务器**必须**返回一个201 Created状态代码和响应文档(如上所述),或一个204 No Content状态代码,没有任何响应文档。
403 禁止
服务器**可以**在响应不支持的创建资源的请求时返回403 Forbidden。
404 未找到
服务器**必须**在处理引用不存在的相关资源的请求时返回404 Not Found。
409 冲突
服务器**必须**在处理使用已存在的客户端生成的 ID 创建资源的POST请求时返回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 确定
如果服务器接受更新,但也以请求中未指定的方式更改了目标资源(例如,更新updatedAt属性或计算的sha),它**必须**返回一个200 OK响应和一个包含更新后的资源作为主要数据的文档。
如果更新成功,并且服务器没有以请求中未指定的方式更改目标资源,则服务器**可以**返回一个200 OK响应,其中包含一个没有主要数据的文档。其他顶级成员,例如meta,可以包含在响应文档中。
202 已接受
如果更新请求已被接受处理,但处理在服务器响应之前尚未完成,服务器**必须**返回202 Accepted状态代码。
204 无内容
如果更新成功,并且服务器没有以请求中未指定的方式更改目标资源,则服务器**必须**返回一个200 OK状态代码和响应文档(如上所述),或一个204 No Content状态代码,没有任何响应文档。
403 禁止
服务器**必须**在响应不支持的更新资源或关系的请求时返回403 Forbidden。
404 未找到
服务器**必须**在处理修改不存在的资源的请求时返回404 Not Found。
服务器**必须**在处理引用不存在的相关资源的请求时返回404 Not Found。
409 冲突
服务器**可以**在处理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 禁止
服务器**必须**返回 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[_] 不是该族中有效的参数名称,因为 _ 不是有效的成员名称。
注意:点分隔的合法成员名称列表旨在用于关系路径。例如,这允许使用关系路径进行过滤策略,如 [排序][获取排序] 中定义的查询参数,例如
GET /posts?sort=author.name&filter[author.status]=active。
扩展特定的查询参数
扩展引入的每个查询参数的基本名称**必须**以扩展的命名空间后跟一个冒号 (:) 为前缀。基本名称的剩余部分**必须**只包含字符 [a-z] (U+0061 到 U+007A,“a-z”)。
实现特定的查询参数
实现**可以**支持自定义查询参数。但是,这些查询参数的名称**必须**来自一个族,其基本名称是一个合法的成员名称,并且还包含至少一个非 a-z 字符(即,不在 U+0061 到 U+007A 之间)。
**建议**使用大写字母(例如驼峰式命名法)来满足上述要求。
如果服务器遇到不符合上述命名约定的查询参数,或者服务器不知道如何将其作为本规范中的查询参数进行处理,则它**必须**返回 400 Bad Request。
注意:通过禁止使用仅包含字符 [a-z] 的查询参数,JSON:API 保留了以后标准化其他查询参数的能力,而不会与现有实现产生冲突。
错误
处理错误
服务器**可以**选择在遇到问题时停止处理,或者**可以**继续处理并遇到多个问题。例如,服务器可能会处理多个属性,然后在单个响应中返回多个验证问题。
当服务器遇到针对单个请求的多个问题时,响应中应该使用最普遍适用的 HTTP 错误代码。例如,对于多个 4xx 错误,400 Bad Request 可能适用,而对于多个 5xx 错误,500 Internal Server Error 可能适用。
错误对象
错误对象提供有关执行操作时遇到的问题的附加信息。错误对象必须作为 JSON:API 文档顶层以 errors 为键的数组返回。
错误对象可以具有以下成员,并且必须包含至少以下成员之一
id: 此特定问题发生的唯一标识符。links: 一个链接对象,它可以包含以下成员status: 适用于此问题的 HTTP 状态代码,表示为字符串值。这应该提供。code: 应用特定的错误代码,表示为字符串值。title: 对问题的简短、人类可读的摘要,不应从问题发生的次序到次序发生变化,除非出于本地化的目的。detail: 对此问题发生的特定情况的人类可读解释。与title类似,此字段的值可以本地化。source: 一个包含对错误主要来源的引用的对象。它应该包含以下成员之一或被省略pointer: 一个指向请求文档中导致错误的值的 JSON 指针 [RFC6901] [例如,对于主要数据对象为"/data",或者对于特定属性为"/data/attributes/title"]。这必须指向请求文档中存在的值;如果不存在,客户端应该简单地忽略指针。parameter: 一个字符串,指示哪个 URI 查询参数导致了错误。header: 一个字符串,指示导致错误的单个请求标头的名称。
meta: 一个元对象,包含关于错误的非标准元信息。
附录
查询参数详细信息
解析/序列化
查询参数是从 URI 的查询字符串中提取或序列化到 URI 的查询字符串中的名称-值对。
为了从 URI 中提取查询参数,实现必须通过application/x-www-form-urlencoded 解析算法运行 URI 的查询字符串(不包括前导问号),但有一个例外: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 生产者(即浏览器)并不总是对它们进行编码。服务器应该接受在查询参数名称中未编码这些方括号的请求。如果服务器接受这些请求,它必须将请求视为等同于对方括号进行百分比编码的请求。