翻译约定
primary data
: 主数据
resource identifier object
资源标识符对象
request
请求
response
响应
return
返回
endpoint
端点
include
/ contain
包含
介绍
JSON API是一个规范,规定了客户端如何请求获取或修改资源,以及服务器如何响应这些请求。
JSON API被设计为:最小化客户端和服务端之间的请求数以及数据传输量。其效率并没有牺牲可读性、灵活性或者可发现性。
JSON API要求使用JSON API媒体类型(application/vnd.api+json
)来交换数据。
约定
文档中的关键词“MUST”、“MUST NOT”、“REQUIRED”、“SHALL”、“SHALL NOT”、“SHOULD”、“SHOULD NOT”、“RECOMMENDED”、“MAY”和“OPTIONAL”按RFC2119中描述的理解。
译注:原文中此段描述的是英文关键词,翻译为中文后,大概有五个意思。“MUST”、“REQUIRED”、“SHALL”表示“必须”。“MUST NOT”和“SHALL NOT”表示“不允许”。“SHOULD”和“RECOMMENDED”表示“应该/建议”。“SHOULD NOT”和“NOT RECOMMENDED”表示“不应该/不建议”。“MAY”和“OPTIONAL”表示“可以/可选的”。
内容协商
客户端的职责
客户端必须在发送所有请求文档的JSON API数据时,使用HTTP头Content-Type: application/vnd.api+json
,但不能使用任何媒体类型参数。
客户端使用 Accept
头包含JSON API媒体类型的,指定媒体类型时必须至少有一次不使用媒体类型参数。
客户端 必须 忽略从响应文档的Content-Type
头获取的application/vnd.api+json
媒体类型的任何参数。
服务端的职责
服务端必须在发送所有响应文档的JSON API数据时,使用HTTP头Content-Type: application/vnd.api+json
,但不能使用任何媒体类型参数。
如果请求中指定了Content-Type: application/vnd.api+json
头,并指定了任何媒体类型参数,服务端必须返回包含状态码415 Unsupported Media Type
的响应。
如果请求的Accept
头包含JSON API的媒体类型,(TODO:并且媒体类型参数改变了这个媒体类型的所有实例),服务端必须返回包含状态码406 Not Acceptable
的响应 。
注解: 由于存在内容协商的要求,允许未来版本的规范使用媒体类型参数去扩展内容协商和版本化。
译注:例如
text/plain;charset=us-ascii
,其中text/plain
是媒体类型,charset=us-ascii
是媒体类型参数。
文档结构
本段描述了JSON API的文档结构。该结构被媒体类型application/vnd.api+json
标注。JSON API中的文档使用JSON[RFC7159]定义。
尽管在请求文档和响应文档中使用了相同的媒体类型,确切的方面只能应用两者中的一个。这种区别会在下面讲述。
除非特别说明,本规范定义的对象不允许包含额外的成员。客户端和服务端的实现必须忽略本规范未确认的成员。
注解: 这种情况允许通过添加性修改来增强本规范。
顶层
从根本上说,每一个包含数据的JSON API请求和响应必须是一个JSON对象。这个对象定义了文档的“顶层”。
一个文档的顶层成员必须至少包含以下成员中的一个:
-
data
: 文档的“主数据” -
errors
: 错误对象的数组 -
meta
: 元数据对象,包含非标准的元数据信息
在同一个文档中,不允许成员data
和errors
同时存在。
一个文档在顶层可以包含以下任意成员:
-
jsonapi
:描述服务端实现的对象 -
links
:与主数据相关的链接对象 -
included
:与主数据和/或 “被包含的资源” 相关的资源对象的数组。
如果文档中不包含顶层的data
键,则不允许included
成员存在。
顶层的链接对象 可以 包含如下成员:
-
self
:被生成的、当前响应文档的链接。 -
related
:当主数据表示一个资源关联时,相关资源的链接。 -
pagination
:主数据的分页链接。
文档的“主数据”表示请求的目标资源或目标资源的集合。
主数据 必须 是以下任意一种:
- 当请求目标为单个资源时,是单个资源对象,单个[资源标识符对象]或者
null
。 - 当请求目标为资源集合时,是资源对象的数组,资源标识符对象的数组或者空数组(
[]
)。
举个例子,下面的主数据是单个资源对象:
{
"data": {
"type": "articles",
"id": "1",
"attributes": {
// ... 这个article的属性
},
"relationships": {
// ... 这个articles的关联
}
}
}
下面的主数据是单个资源标识符对象,它引用了相同的资源:
{
"data": {
"type": "articles",
"id": "1"
}
}
即便只有一个条目或是没有条目,资源集合也必须用数组表示,这是符合逻辑的。
资源对象
在JSON API文档中出现的“资源对象”用来表示资源。
一个资源对象在顶层必须至少包含以下成员中的一个:
id
type
例外:当资源对象是由客户端发起,并且是需要在服务端创建的新的资源时,id
成员不是必须的。
另外,资源对象在顶层可以包含任意以下成员:
-
attributes
:一个属性对象,表示资源的某些数据。 -
relationships
:一个关联对象,表示一个资源对象和另一个资源对象之间的关联。 -
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" }
}
}
}
// ...
标识符
每一个 资源对象 必须 包含一个id
成员和一个 type
成员。id
和 type
成员的值 必须是字符串。
对于给定的API,每个资源对象的type
、id
对必须唯一标识一个资源。API是由一组URI构成的,控制这些URI的,可以是单个服务端,也可以是行为上表现为单个服务端的一组服务端。
type
成员被用来描述共享通用属性和关联的资源对象。
type
成员的值必须和成员名遵循相同的约束.
注意:这份规范对词形变化是持不可知论的,所以
type
的值可以是单数,也可以是复数。不管怎样,在同一份实现中应该保持相同的值。
字段
资源对象的属性和关联统称为资源对象的“字段”。
资源对象的字段 必须 与type
和id
共享同一个命名空间。换句话说,一个资源不能在属性和关联中使用相同的名字,当然也不能使用type
和id
作为名字。
属性
attributes
的值必须是一个对象,即一个“attributes”对象。“attributes”对象的成员表示其定义的资源对象的信息。
属性可以包含任意合法的JSON值。
(TODO:涉及JSON对象和数组的复杂的数据结构也被允许作为属性的值)。不管怎样,任何组成或包含在属性中的对象,都不允许 包含relationships
或links
成员,因为它们在本规范中是保留字,以供将来使用。
尽管 has-one 外键 (比如author_id
) 通常会和其他信息一同保存在资源对象的内部,但这类键不应该作为属性出现。
注解:参看 字段 和 成员名 来获取关于这个容器的更多的限制。
关联
relationships
的值必须是一个对象,即一个“relationships”对象。“relationships”对象的成员表示其定义的其他资源对象的信息。
关联可以是to-one或to-many的。
一个“关联对象”必须包含以下至少一个:
-
links
:一个链接对象,至少包含以下中的一个:-
self
:一个指向自身的链接(即这个关联自身的链接)。客户端可使用这个链接直接操作关联。例如, 通过article
的关联URL删除author
会断开people
资源与article
的联系,但是并不会删除people
资源本身。当成功获取数据,这个链接会为相关资源返回资源链接,并作为其主数据。(参看获取关联) -
related
:表示一个相关资源链接
-
-
data
:联合的资源 -
meta
:一个元数据对象表示关于该关联的非标准的元数据信息。
一个表示to-many的关联还可以在links
成员中包含分页链接,后文将会提到。
注解:参看字段和成员名 获取更多关于该容器的限制信息。
相关资源的链接
一个“相关资源的链接”提供了访问在关联中链接的资源对象。一旦取得资源对象,相关的资源对象会作为响应的主数据返回。
例如,当通过GET
请求取回数据时,一个article
(文章)和comments
(评论)的关联能够指定一个返回评论资源对象的集合的链接。
如果相关资源存在,其链接必须引用一个合法的URL,即使这个关联当前并没有关联到任何的目标资源。另外,相关资源链接不允许因为关联的内容改变而改变。
资源联合
复合文档中的资源联合,允许客户端将所有被包含的资源对象聚合在一起,而不需要GET任何URL链接。
资源联合必须用以下方式中的一种表示:
-
null
,表示空的to-one关联。 - 空数组(
[]
),表示空的to-many关联。 - 单个的资源标识符对象,表示非空的to-one关联。
- 资源标识符对象的数组,表示非空的to-many关联。
注解: 本规范并没有明确告知(TODO:在to-many关联中联合的资源标识符对象的排序)的意义,尽管实现上可以这么做。资源标识符对象的数组可以表示为已排序的,也可以是未排序的,还可以在一个响应对象中两者兼而有之。
例如,下面的文章与一个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
成员。
“资源标识符对象”也可以包含一个meta
成员,该成员的值是一个元数据对象,这个对象包含非标准的元信息。
复合文档
为了减少HTTP请求的数量,服务端可以将请求的主数据连同相关数据一并放入响应中。这样的响应被称为“复合文档”。
在复合文档中,所有被包含的资源必须在成员included
的顶层,用资源对象的数组表示。
复合文档要求“完全链接”,也就是说每一个被包含的资源,在相同的文档中,必须被至少一个资源标识符对象标识。这些资源标识符对象既可以是主数据,也可以是包含在主数据或被包含的数据中所联合的资源。
对完全联合的要求的唯一例外是,当关联字段额外包含了通过稀疏字段排除的联合数据。
注解:(TODO:)完全联合保证包含进来的资源要么关联到主数据 (主数据可以是资源对象,也可以是资源标识符对象),要么是主数据和资源标识符对象互相关联。
一个包含多个关联的完整的示例文档:
{
"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": {
"first-name": "Dan",
"last-name": "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
认为是一个复合的键,这个键在文档的其他部分唯一的引用资源对象。
注解: 这种方式确保了对每一个响应所返回的,是唯一权威的 资源对象 ,即使这个资源被多次引用。
元信息
指定meta
成员的地方,能够用来包含非标准的元信息。meta
成员的值 必须是一个meta
对象。
任何成员都可以指定meta
对象。
举个例子:
{
"meta": {
"copyright": "Copyright 2015 Example Corp.",
"authors": [
"Yehuda Katz",
"Steve Klabnik",
"Dan Gebhardt",
"Tyler Kellen"
]
},
"data": {
// ...
}
}
链接
links
成员用来表示链接。每一个links
的值必须是一个对象,即一个“links”对象。
链接对象的每一个成员都是一个“链接”。一个链接必须用下列方式中的一种表示:
- 一个包含URL的字符串。
- 一个链接对象,可以包含如下成员:
-
href
: 一个包含URL的字符串。 -
meta
: 一个元数据对象,包含了关于该链接的非标准的元信息。
-
下面的self
链接很简单,是一个URL:
"links": {
"self": "http://example.com/posts"
}
下面的 related
链接 包含一个URL,同时包含了与资源集合有关的元信息:
"links": {
"related": {
"href": "http://example.com/articles/1/comments",
"meta": {
"count": 10
}
}
}
注解: 在未来,可能会有附加成员被指定到链接对象。也可能会扩展附加成员所支持的值(例如,
collection
链接可能支持数组值,但是self
链接不支持)。
JSON API 对象
一个JSON API文档可以 在顶层的jsonapi
成员中包含关于其实现的信息。如果jsonapi
成员存在,其值必须是一个对象,即“JSON API”对象。jsonapi对象可以包含一个version
成员,该成员的值是字符串类型,用于指出被支持的最高的JSON API版本。这个对象也可以包含一个meta
成员,该成员的值是一个包含非标准信息的元数据对象。
{
"jsonapi": {
"version": "1.0"
}
}
如果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字符;不推荐使用,因为其不是URL安全的)
此外,下列字符也可以用在成员名中,但是不能放在开头或末尾:
- U+002D 连字符(减号),“-”
- U+005F 下划线,“_”
- U+0020 空格,“ ”(不推荐使用,因为其不是URL安全的)
保留字
以下字符 不允许 用于成员名:
- U+002B 加号,“+” (用于排序)
- U+002C 逗号,“,” (作为between关联的路径分隔符)
- U+002E 句点,"." (作为within关联的路径分隔符)
- U+005B 左方括号, "[" (用于稀疏字段集)
- U+005D 右方括号, "]" (用于稀疏字段集)
- U+0021 感叹号, "!"
- U+0022 双引号, '"'
- U+0023 井号, "#"
- U+0024 美元符, "$"
- U+0025 百分号, "%"
- U+0026 and号, "&"
- U+0027 撇号, "'"
- U+0028 左小括号, "("
- U+0029 右小括号, ")"
- U+002A 星号, "*"
- U+002F 斜杠, "/"
- U+003A 冒号, ":"
- U+003B 分号, ";"
- U+003C 小于号 "<"
- U+003D 等于号 "="
- U+003E 大于号, ">"
- U+003F 问号, "?"
- U+0040 @号, "@"
- U+005C 反斜杠, ""
- U+005E 抑扬音符号, "^"
- U+0060 重音号, "`"
- U+007B 左大括号, "{"
- U+007C 竖线, "|"
- U+007D 右大括号, "}"
- U+007E 波浪线, "~"
- U+007F Unicode字符“DELETE(删除)”
- U+0000 到 U+001F (C0 控制位)
获取数据
使用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
。
服务端必须在成功请求一组资源后返回资源对象的数组或者一个空数组 ([]
) 作为响应文档的主数据.
例如,一个对文章(articles)的集合的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
作为主数据。
仅当请求URL是对应目前不存在的单个资源时,null
才是一个恰当的响应。
注解:思考一下,现在有一个请求去获取to-one关联的相关资源的链接。当关联是空的(例如这个链接并没有相应的资源),但是另外有单个的相关资源的资源对象,这个请求会收到
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语义
准备好响应数据,客户端也必须 按照HTTP语义
解释这些响应数据。
获取文档的关联
服务端必须支持对每一个关联的links
对象,从其self
部分提供的关联URL获取关联数据。
例如,下面的请求获取一篇文章(article)的评论(comment):
GET /articles/1/relationships/comments HTTP/1.1
Accept: application/vnd.api+json
下面的请求获取一篇文章(article)的作者(author):
GET /articles/1/relationships/author HTTP/1.1
Accept: application/vnd.api+json
响应
200 OK
请求成功获取了关联,服务端必须 响应200 OK
。
响应的主数据必须匹配恰当的资源联合,正如关联对象中所述。
顶层的链接对象可以 包含self
和 related
链接,正如关联对象中所述。
例如,从to-one关联链接发起的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
}
从to-many关联链接发起的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语义
准备好响应数据,客户端也必须 按照HTTP语义
解释这些响应数据。
关联资源的包含
默认情况下,一个端点可以在主数据中返回相关资源。
端点也可以支持一个带include
参数的请求,以允许客户端自定义哪些相关资源可以被返回 。
如果端点不支持include
参数,则对任何带有include
参数的请求,都必须返回400 Bad Request
响应。
如果端点支持include
参数,并且客户端在请求中也应用了这个参数,服务端不允许在复合文档的included
段包含未被请求的资源对象 。
include
参数的值必须是逗号(U+002C 英文逗号 “,”)分隔的关联路径的列表。关联路径是点号(U+002E 英文句点 “.”)分隔的关联名称的列表。
如果服务端不能识别某个关联路径,或者不支持从某个路径包含资源,服务端必须响应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
和其author
。
注解: 服务端可以选择揭露底层嵌套关联,例如直接关联
comments.author
有一个comment-authors
别名。这允许客户端通过请求/articles/1?include=comment-authors
替代/articles/1?include=comments.author
。简要地说就是,嵌套关联有一个别名时,服务端依然能够完全联合复合文档,而不用包含潜在的、不需要的、中间的资源。
多个关联资源可以通过逗号分隔的列表请求:
GET /articles/1?include=author,comments.author 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
参数的值必须是一个逗号(U+002C COMMA, ",")分隔的列表,这个列表引用返回字段的名字。
如果客户端请求一个给定资源类型的集合,并对字段进行了限制,则端点在响应中不允许在这个类型的资源对象中包含另外的字段。
GET /articles?include=author&fields[articles]=title,body&fields[people]=name HTTP/1.1
Accept: application/vnd.api+json
注解:上面例子中的展示的URI,为了可读性而简单的使用了unencode编码的
[
和]
字符。在实践中,这些字符必须被“百分号”编码,所有的要求都在RFC 3986中。
注解: 本段应用到任何作为主数据或被包含的数据的资源而响应的端点,而不用管请求的类型。例如,服务端能够支持用于创建资源的
POST
请求所携带的稀疏字段。
排序
服务端可以选择支持按照一个或多个标准(即“排序字段”)对资源集合进行排序的请求。
注解:排序字段并不是必须同资源属性和关联名一致,尽管推荐这么做。
注意:(TODO:)推荐使用点号(U+002E 英文句点 “.”)分隔的排序字段用于那些基于关联属性的排序请求。例如,一个排序字段
author.name
能够被用于主数据基于author
关联的name
属性排序。
一个端点可以支持那些带一个sort
查询参数的对主数据的排序请求。sort
的值必须代表排序字段。
GET /people?sort=age HTTP/1.1
Accept: application/vnd.api+json
一个端点可以支持多字段排序,通过允许逗号(U+002C 英文逗号 ",") 分隔的排序字段。排序字段应该被应用于指定的命令。
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
应用默认的排序规则。
注解: 本段应用到任何作为主数据或被包含的数据的资源而响应的端点,而不用管请求的类型。
分页
服务端可以选择通过整个集合的一个子集“page”来限制响应中返回的资源的数量。
(TODO:)服务端可以提供链接,在分页数据集(“分页链接”)之间来回移动。
分页链接必须依据集合在链接对象中出现。对于已分页的主数据,在顶层links
对象中提供分页链接。对于返回的复合文档中所包含的集合,在相应的对象中提供分页链接。
下列键 必须用在分页链接中:
-
first
:数据的第一页 -
last
:数据的最后一页 -
prev
:数据的上一页 -
next
:数据的下一页
为了表明特定的分页链接是不可用的,必须忽略键或将其设置为null
值。
排序的概念,(TODO:)诸如用分页链接的命名表述,必须遵照JSON API的排序规则的限制。
page
是为分页保留的查询参数。服务端和客户端应该使用这个键用作分页操作。
注解: JSON API对服务端的分页策略持不可知论。高效的分页策略包括但不限于:基于页的、基于偏移量的、基于游标的。查询参数
page
能够基于以上策略。例如,一个基于页的策略也许会使用诸如page[number]
和page[size]
的查询参数,一个基于游标的策略也许会使用page[cursor]
。
注解: 上面的关于查询参数的例子,为了可读性而简单的使用unencode编码的
[
和]
字符。在实践中,这些字符必须被“百分号”编码,所有的要求都在in RFC 3986。
注解: 本段应用到任何作为主数据或被包含的数据的资源而响应的端点,而不用管请求的类型。
过滤
查询参数filter
是为过滤数据保留的。服务端和客户端应该 使用这个参数来执行过滤操作。
注解: JSON API对服务端支持的过滤策略持不可知论。能够使用任意数量的过滤参数
filter
。
创建、更新和删除资源
服务端可以创建给定类型的资源,也可以对已存在的资源进行修改和删除。
一个请求,在单个“事务”中,要么必须成功,要么必须失败。不允许“部分成功”的更新。
注解:JSON API中,所有请求和响应中每一个资源对象的
type
成员是必须的。有许多用例,例如,当type
不能从端点推测出来时,向一个端点发送POST
请求,这个请求表示一个异构的数据。不管怎样,当你困惑的时候,请谨慎的选择。应该牢牢记住,什么时候需要使用,什么时候不需要。因此,为了增强一致性和�最小化混乱,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
成员的关联对象。data
的值表示这个新资源将会持有的链接。
客户端生成资源ID
服务端可以接受一个随着请求来创建资源的由客户端生成的ID。ID必须通过键id
来指定,其值必须是全局唯一的标识符。客户端应该用恰当生成的和格式化的UUID。关于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"
}
}
}
服务端必须对不支持的由客户端生成的ID、创建资源的请求,在响应中返回403 Forbidden
。
响应
201 Created
如果POST
请求中不包含客户端生成的ID,并且被请求的资源被成功创建,那么服务端必须返回201 Created
状态码。
响应应该包含一个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"
}
}
}
202 Accepted
如果创建资源的请求被接收并开始处理,但是服务端返回响应时,处理过程并没有完成,这时服务端必须返回202 Accepted
状态码。
204 No Content
如果POST
请求中包含客户端生成的ID,并且被请求的资源被成功创建,那么服务端必须返回201 Created
状态码并在响应中包含文档,或返回204 No Content
状态码,但不返回响应文档。
注解:如果收到
204
,客户端应该关心请求中发送的被服务端接受的资源对象,好像服务端用201
作为响应。
403 Forbidden
服务端可以对一个用于创建资源的不被支持的请求返回响应403 Forbidden
。
404 Not Found
当处理一个请求不存在的相关引用资源,服务端必须返回404 Not Found
。
409 Conflict
当处理一个带着已经存在的客户端生成ID,用于创建资源的post
请求,服务端必须返回409 Conflict
。
当处理一个post
请求,资源对象的type
类型不属于那些由端点描述的组成集合的类型,服务端必须返回409 Conflict
。
服务端应该包含错误细节并提供足够的信息来识别发生冲突的源头。
其他响应
服务端可以使用其他HTTP状态码作为响应。
服务端可以在返回错误时包含错误细节。
服务端必须准备好响应数据,并且客户端必须 按照HTTP语义
解释这些响应数据。
更新资源
通过发送PATCH
请求可以更新URL表示的资源。
资源的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"
}
}
}
更新资源的属性
资源的任何或全部[属性][attributies]可以被包含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
成员的关联对象。关联的值会被这个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" }
]
}
}
}
}
服务端可以拒绝完整替换to-many关联的企图。在一些用例中,服务端必须拒绝整个更细,并且返回403 Forbidden
响应。
注解:因为完全的替换可能是一个非常危险的操作,服务端可能会选择不接受。例如,如果没有给客户端提供完整的关联对象的列表,服务端可能拒绝完全的替换,并且也不会想要允许删除客户端未曾见过的记录。
响应
202 Accepted
如果一个更新请求被接收并开始处理,但是服务端返回响应时,处理过程并没有完成,这时服务端必须返回202 Accepted
状态码。
200 OK
如果接受了一个更新请求,同时用请求中指定的那些方式之外的方式修改了资源(例如,更新了一个updated-at
属性,或者一个计算属性sha
),服务端必须返回200 OK
响应。响应文档必须包含一个更新后的资源的表示,就好像向这个请求URL发送GET
请求那样。
如果更新成功了,客户端当前的属性保持是最新的,服务端响应值包含顶层的元对象数据,服务端必须返回200 OK
状态码。在这时,服务端不允许包含已被更新的资源。
204 No Content
如果更新成功了,并且服务端没有更新任何没有提供的属性,那么服务端必须要么返回200 OK
状态码和响应如前所属的文档,要么返回204 No Content
状态码而没有响应文档。
403 Forbidden
服务端必须对不支持的更新资源或关联请求,返回响应403 Forbidden
。
404 Not Found
当一个请求是用于处理一个引用了不存在的相关资源时,服务端必须返回404 Not Found
。
409 Conflict
当处理一个PATCH
请求去更新资源,如果更新操作会违反服务端强制的限制(比如除id
外的property上的唯一性限制),服务端可以返回409 Conflict
。
当处理一个PATCH
请求,其中资源对象的type
和id
与服务端的不匹配,服务端必须返回409 Conflict
。
服务端应该包含错误细节并提供足够的信息来识别发生冲突的源头。
其他响应
服务端可以使用其他HTTP状态码作为响应。
服务端可以在返回错误时包含错误细节。
服务端必须准备好响应数据,并且客户端必须 按照HTTP语义
解释这些响应数据。
更新文档的关联
尽管关联可以随着资源一并修改(如上所述),但JSON API也支持独立地通过 关联链接的URL更新关联。
注解:可以在不揭露底层的服务端语义的情况下更新关联,例如外键。此外,可以在不必影响相关资源的情况下更新关联。 例如,如果一篇文章有多个作者,在不删除作者的情况下,是可以从作者中删除一个的。相似地,如果一篇文章有多个标签,添加或删除标签也是可以的。在服务端的引擎盖之下,这些例子中,第一个可能会使用外键实现,第二个可能会使用连接表实现,但是JSON API协议在这两个案例中是相同的。
注解: 如果关联被删除了(as a garbage collection measure),服务端可能会选择删除底层数据。
更新“to-one”关联
服务端必须 对如下描述的从to-one关联链接的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
}
如果成功更新的关联,服务端必须返回一个成功的响应。
更新“to-many”关联
服务端必须对那些向to-many(后面会提到)关联的URL发起的PATCH
、POST
和DELETE
的请求作出响应。
对于所有类型的请求,请求体必须包含一个data
成员,这个成员的值要么是空数组,要么是资源标识符对象的数组。
如果客户端对to-many关联的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": []
}
如果客户端从关联链接发起一个POST
请求到URL,服务端必须添加指定的成员到关联中,除非这些关联已经存在。如果给定的type
和id
已经在关联中存在,不允许服务端再将它们添加进去。
注解:这符合数据库使用外键表示has-many关联的语义。文档型存储应该在添加数据之前进行检查has-many关联,以免出现重复记录。
如果指定的所有资源都可以被添加,或者已经存在,那么服务端必须返回成功的响应。
注解:这种方式确保了,如果服务端的状态满足请求的状态,并且帮助避免由多个客户端同时对一个关联做相同的修改带来的无意义的竞态条件,请求都是成功的。
下面的例子中,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为1
的文章的评论列表中移除了ID为12
和13
的评论:
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。
Responses
202 Accepted
如果一个更新关联的请求被接收并开始处理,但是服务端返回响应时,处理过程并没有完成,这时服务端必须返回202 Accepted
状态码。
204 No Content
如果成功更新,并且请求中资源的表示与结果相匹配,服务端必须返回204 No Content
状态码。
注解: 这是当关联已经存在时,从to-many 关联的URL的
POST
请求的恰当的响应。同时也是发送DELETE
请求到to-many 关联的URL时,关联不存在时,的恰当的响应。
200 OK
如果接受了一个更新请求,同时用请求中指定的那些方式之外的方式修改了目标关联,服务端必须返回200 OK
响应。响应文档必须包含一个更新后的关联的表示。
如果更新成功了,客户端当前数据是最新的,服务端的响应值包含顶层的元数据,服务端必须返回200 OK
状态码。在这个用例中,服务端不允许包含已更新的关联的表示。
403 Forbidden
服务端必须在不支持的对关联的更新请求的响应中返回403 Forbidden
。
其他响应
服务端可以使用其他HTTP状态码作为响应。
服务端可以在返回错误时包含错误细节。
服务端必须准备好响应数据,并且客户端必须 按照HTTP语义
解释这些响应数据。
删除资源
一个独立的资源可以被删除,通过发送DELETE
请求到资源的URL上:
DELETE /photos/1 HTTP/1.1
Accept: application/vnd.api+json
响应
202 Accepted
如果一个删除请求被接受并处理,但是在服务器返回响应时,处理过程并没有结束,这时服务端必须返回202 Accepted
状态码。
204 No Content
如果一个删除请求成功了,并且没有内容需要返回,服务端必须返回204 No Content
状态码。
200 OK
如果一个删除请求成功了,并且服务端只返回顶层的元数据,这时必须返回200 OK
状态码。
404 NOT FOUND
如果一个删除请求因资源不存在而失败,服务端应该返回404 Not Found
状态码。
其他响应
服务端可以使用其他HTTP状态码作为响应。
服务端可以在返回错误时包含错误细节。
服务端必须准备好响应数据,并且客户端必须 按照HTTP语义
解释这些响应数据。
查询参数
特定的查询参数的实现必须与成员名遵循相同的约束,而且额外要求必须包含至少一个非a-z(U+0061到U+007A)的字符。推荐使用减号(U+002D “-”)、下划线(U+005F “_”)或者大写字母(例如camelCasing)。
如果遇到一个没有遵循上面的命名约定的请求参数,并且按照本规范,服务端不知道如何处理这个请求参数时,服务端必须返回400 Bad Request
。
注解:这里保留了向JSON API的标准查询参数中做出添加附加物又不会与现有实现冲突的能力。
错误
处理错误
服务端在遇到问题时可以立即停止处理,也可以继续处理,并遇到encounter多个问题。例如,服务端可能处理很多属性,然后在一个响应中返回很多验证方面的问题。
当服务端在一个请求中遇到多个问题,最通用的、适合HTTP错误码的应该在响应中使用。例如,当有多个4xx的错误时,400 Bad Request
可能会比较合适;或者,当有多个5xx的错误时,500 Internal Server Error
可能会比较合适.
错误对象
错误对象提供了关于处理一个操作时遇到的问题的额外的信息。错误对象必须以数组的形式被返回,并在JSON API文档的顶层使用errors
作为键。
一个错误对象可以有如下成员:
-
id
: 一个对这个问题的详细描述的唯一的标识符。 -
links
: 一个包含下列成员的链接对象:-
about
:一个对这个问题的详细描述更进一步的细节的链接 。
-
-
status
:适用于这个问题的HTTP状态码,用字符串类型表示。 -
code
:应用程序指定的错误码,用字符串表示。 -
title
:对问题简短的、人类可读的摘要。多次发生时,这个摘要不应该改变,除非是出于本地化的目的。 -
detail
:对于这个事件的人类可读的解释。和title
类似,这个字段的值可以被本地化。 -
source
:一个对象,持有错误来源的引用,可选地包含下列成员:-
pointer
:在请求文档中的一个指向相关实体的JSON指针[RFC6901](译注:类似XML中的XPath)[例如"/data"
指向主数据对象,"/data/attributes/title"
指向指定的属性]。 -
parameter
:标明是哪个查询参数引起的错误,是一个字符串。
-
-
meta
:包含关于错误的非标准的元信息的元对象。