├── .gitignore ├── CNAME ├── README.md ├── _config.yml ├── _includes ├── footer.html ├── header.html └── status.md ├── _layouts └── page.html ├── about └── index.md ├── design └── jsonapi.sketch │ ├── Data │ ├── metadata │ └── version ├── examples └── index.md ├── extending └── index.md ├── faq └── index.md ├── format └── index.md ├── images ├── blockquote.gif ├── forkme_right_red_aa0000.png ├── jsonapi@0.7x.png └── jsonapi@1.3x.png ├── index.md ├── javascripts ├── all.js └── highlight.pack.js ├── recommendation └── index.md ├── schema ├── status └── index.md └── stylesheets ├── all.css ├── highlight.css └── normalize.css /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | # 3 | # If you find yourself ignoring temporary files generated by your text editor 4 | # or operating system, you probably want to add a global ignore instead: 5 | # git config --global core.excludesfile ~/.gitignore_global 6 | 7 | Gemfile.lock 8 | 9 | # Ignore bundler config 10 | /.bundle 11 | 12 | # Ignore the build directory 13 | /public 14 | 15 | # Ignore Sass' cache 16 | /.sass-cache 17 | 18 | /bin 19 | /b 20 | .rbx 21 | -------------------------------------------------------------------------------- /CNAME: -------------------------------------------------------------------------------- 1 | jsonapi.org.cn 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | JSON API:用 JSON 构建 API 的标准指南中文版 2 | ======== 3 | 4 | 本项目是网站 [http://jsonapi.org.cn](http://jsonapi.org.cn) 的源码。 5 | 6 | 英文官方网址:[http://jsonapi.org](http://jsonapi.org)。 7 | 8 | 资源 9 | --------- 10 | 11 | * IRC channel: #jsonapi on freenode.net 12 | * Twitter: @jsonapi 13 | 14 | 翻译 15 | --------- 16 | 17 | * [justjavac(迷渡)](http://github.com/justjavac) 18 | * [bornkiller](http://github.com/bornkiller) 19 | 20 | 21 | 现状 Status 22 | ------ 23 | 24 | 本标准是目前正在开发中。如需贡献: 25 | 26 | 1. 获取所有依赖: 27 | 28 | `$ bundle` 29 | 30 | 1. 搭建本地站点: 31 | 32 | `$ bundle exec rake preview:browser` 33 | 34 | 1. 编辑 Markdown 文件。 35 | 1. 提交更改。 36 | 1. 提交 Pull Request。 37 | 38 | 当更改被合并到 `gh-pages` 分支,本站点将由 [GitHub Pages](http://pages.github.com) 自动构建。 39 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | url: http://jsonapi.org.cn 2 | safe: true 3 | lsi: false 4 | source: . 5 | destination: ./public 6 | exclude: 7 | - README.md 8 | - CNAME 9 | - .gitignore 10 | - ./public 11 | - ./stylesheets/*.scss 12 | markdown: redcarpet 13 | pygments: false 14 | port: 9876 15 | 16 | navigation: 17 | - title: 概述 18 | url: / 19 | - title: 格式 20 | url: /format/ 21 | - title: 扩展 22 | url: /extending/ 23 | - title: 示例 24 | url: /examples/ 25 | - title: FAQ 26 | url: /faq/ 27 | - title: 关于 28 | url: /about/ 29 | -------------------------------------------------------------------------------- /_includes/footer.html: -------------------------------------------------------------------------------- 1 | 30 | -------------------------------------------------------------------------------- /_includes/header.html: -------------------------------------------------------------------------------- 1 | Fork me on GitHub 2 | 3 |
4 |
5 | 8 | 18 |
19 |
20 | -------------------------------------------------------------------------------- /_includes/status.md: -------------------------------------------------------------------------------- 1 | ## 现状 2 | 3 | **本文档是一个正在进展的工作**,在具体实现过程中将会有所改变。详细信息请查看[现状](/status)页面。 4 | -------------------------------------------------------------------------------- /_layouts/page.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | JSON API :: {{ page..title }} 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | {% include header.html %} 23 |
24 | 25 |
26 | {% if page.show_masthead %} 27 |
28 | JSON API 29 |

{{ page.title }}

30 |
31 | {% else %} 32 |

{{ page.title }}

33 | {% endif %} 34 | {{ content }} 35 |
36 |
37 | {% include footer.html %} 38 | 39 | 40 | -------------------------------------------------------------------------------- /about/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page 3 | title: About 4 | --- 5 | 6 | ## 频道 7 | 8 | * [@jsonapi](http://twitter.com/jsonapi) on Twitter 9 | * _#jsonapi_ channel on [Freenode IRC](http://freenode.net) 10 | * [jsonapi Google group](https://groups.google.com/forum/?fromgroups#!forum/jsonapi) 11 | 12 | ## 编辑者 13 | 14 | 该规范的两个主要编辑者: 15 | 16 | - Steve Klabnik:twitter [@steveklabnik](http://twitter.com/steveklabnik) 17 | - Yehuda Katz:twitter [@wycats](http://twitter.com/wycats) 18 | 19 | 翻译者: 20 | 21 | - [justjavac](http://github.com/justjavac) 22 | - [bornkiller](http://github.com/bornkiller) 23 | 24 | > 然而,对于 Web 来说,最重要的是这种关注点的分离允许组件独立地进化, 25 | > 从而支持多个组织领域的 Internet 规模的需求。 26 | > 27 | > —— Roy Fielding 《架构风格与基于网络的软件架构设计》 [第五章](http://www.ics.uci.edu/~fielding/pubs/dissertation/rest_arch_style.htm) 28 | 29 | (译注:此书第五章介绍了 RESTful 风格的网络架构 by [@justjavac](http://weibo.com/justjavac)) 30 | 31 | Steve Klabnik 主要是服务器端,而 Yehuda Katz 则是客户端。 32 | 而我们两者都关注,但我们要确保两边都有一个捍卫者。 33 | 34 | ## 历史 35 | 36 | JSON API 来自 JSON 的数据传输,它被隐式地定义在 [Ember](http://emberjs.com/) 的 REST 风格数据适配器。 37 | 38 | 一般来说,Ember Data 被设计用来实现这样的目的:消除哪些为不同应用程序与服务器之间通信而写的特殊代码, 39 | 而是用 REST 风格数据适配器将它们转换成统一的方式。 40 | 41 | 一些服务器,比如 Firebase、Parse 和 CouchDB 已经定义了和客户端通信的精确的协议,以便适合 Ember 数据。 42 | 相比之下,用 Rails、Node.js、 Django 编写的服务器端程序,倾向于使用 REST 风格, 43 | 但对于客户端程序,却没有多少是使用 REST 风格的。 44 | 45 | Ember Data 的 REST Adapter 隐式定义了一个协议, 46 | 服务器需要实现此协议,用来为客户端程序提供有所的资源。 47 | [ActiveModel::Serializers][1] 是 Rails 的一个库,并实现了 Ember 48 | Data 所期望的序列化格式。 49 | 50 | [1]: https://github.com/rails-api/active_model_serializers 51 | 52 | Ember Data 关于记录的创建、更新和删除, 53 | 已经成为 Rails、Django 和 Node.js 开发者广泛使用的约定。 54 | 55 | 媒体类型的目标是追求均衡: 56 | 57 | * 一个通用的媒体类型,可以在非常广泛的场景中使用,包括常用的关系类型 58 | * 对现有服务器端框架最佳实践类似(可读性与可调试性) 59 | * 易于在服务器端实现 60 | * 易于在客户端实现 61 | 62 | 该类媒体仍然是一个进展中的工作,我们是非常开放的反馈和建议改进。 63 | 就是说,该规范的实现工作已经开始,我们的价值在于提供更好的雾件(vaporware)。 64 | -------------------------------------------------------------------------------- /design/jsonapi.sketch/Data: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/justjavac/json-api-zh_CN/f33dd60e301338a59829ee2a2bfdabdff7ddc6e7/design/jsonapi.sketch/Data -------------------------------------------------------------------------------- /design/jsonapi.sketch/metadata: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | app 6 | com.bohemiancoding.sketch3 7 | build 8 | 7597 9 | commit 10 | 463d76bf23dc890960e184011f80b3dacac572b9 11 | fonts 12 | 13 | AmericanTypewriter 14 | 15 | length 16 | 8333 17 | version 18 | 36 19 | 20 | 21 | -------------------------------------------------------------------------------- /design/jsonapi.sketch/version: -------------------------------------------------------------------------------- 1 | 36 -------------------------------------------------------------------------------- /examples/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page 3 | title: Examples 4 | --- 5 | 6 | Examples are excellent learning aids. The following projects implementing JSON 7 | API are divided into server- and client-side. The server-side is further 8 | divided by implementation language. If you'd like your project listed, [send a 9 | Pull Request](https://github.com/json-api/json-api). 10 | 11 | ## Client 12 | 13 | ### JavaScript 14 | 15 | * [ember-data](https://github.com/emberjs/data) is one of the original exemplar 16 | implementations. There is a [custom adapter](https://github.com/daliwali/ember-json-api) to support json-api. 17 | 18 | * [backbone-jsonapi](https://github.com/guillaumervls/backbone-jsonapi) is a Backbone adapter for JSON API. Supports fetching Models & Collections from a JSON API source. 19 | 20 | ### iOS 21 | 22 | * [jsonapi-ios](https://github.com/joshdholtz/jsonapi-ios) is a library for loading data from a JSON API datasource. Parses JSON API data into models with support for auto-linking of resources and custom model classes. 23 | 24 | ## Server 25 | 26 | ### PHP 27 | 28 | * [FriendsOfSymfony / FOSRestBundle](https://github.com/FriendsOfSymfony/FOSRestBundle/issues/452) 29 | 30 | ### Node.js 31 | 32 | * [Fortune.js](http://fortunejs.com) is a framework built to implement json-api. 33 | 34 | ### Ruby 35 | 36 | * [ActiveModel::Serializers](https://github.com/rails-api/active_model_serializers) 37 | is one of the original exemplar implementations, but is slightly out of date at 38 | the moment. 39 | 40 | * [JsonApiClient](https://github.com/chingor13/json_api_client) attempts to give you a query building framework that is easy to understand (similar to ActiveRecord scopes) 41 | 42 | * [The rabl wiki](https://github.com/nesquena/rabl/wiki/Conforming-to-jsonapi.org-format) 43 | has a page describing how to emit conformant JSON. 44 | 45 | * [RestPack::Serializer](https://github.com/RestPack/restpack_serializer) implements the read elements of json-api. It also supports paging and side-loading. 46 | 47 | * [Oat](https://github.com/ismasan/oat#adapters) ships with a JSON API adapter. 48 | 49 | ### Python 50 | 51 | * [Hyp](https://github.com/kalasjocke/hyp) is a library for creating json-api responses. 52 | 53 | * [SQLAlchemy-JSONAPI](https://github.com/coltonprovias/sqlalchemy-jsonapi) provides JSON API serialization for SQLAlchemy models. 54 | 55 | ## Messages 56 | 57 | * [RestPack::Serializer provides examples](http://restpack-serializer-sample.herokuapp.com/) which demonstrate sample responses. 58 | 59 | ## Related Tools 60 | 61 | ### Ruby 62 | 63 | * [json-patch](https://github.com/guillec/json-patch) implementation of JSON Patch (rfc6902) 64 | 65 | * [hana](https://github.com/tenderlove/hana) implementation of the JSON Patch and JSON pointer spec 66 | 67 | ### Node.js 68 | 69 | * [json-patch](https://www.npmjs.org/package/json-patch) implementation of JSON Patch (rfc6902) 70 | -------------------------------------------------------------------------------- /extending/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page 3 | title: 扩展 4 | --- 5 | 6 | {% include status.md %} 7 | 8 | ## 扩展 9 | 10 | 如果你想扩展 JSON API,你应该遵循 [RFC 6906](http://tools.ietf.org/html/rfc6906) 定义的 profile。 11 | 另请参阅 [Mark Nottingham 写的这篇文章](http://www.mnot.net/blog/2012/04/17/profiles)。 12 | 13 | `meta` 部分定义了 profile 的链接。 14 | 15 | **注意**,在 RFC 规范中,profile: 16 | 17 | > 不改变资源表示的语义定义本身,但让客户了解更多的语义(约束、规范、扩展)相关联的资源表示形式, 18 | > 还有这些定义的媒体类型和可能的其他机制。 19 | 20 | ## 示例 21 | 22 | 例如,假设你想让你的 API 支持不同的分页设计,如基于游标。 23 | 你会制作某种信息页面在你的网站上,如 `http://api.example.com/profile`, 24 | 然后会响应中包含 `meta` 键: 25 | 26 | ```text 27 | GET http://api.example.com/ 28 | ``` 29 | 30 | ```json 31 | { 32 | "meta": { 33 | "profile": "http://api.example.com/profile" 34 | }, 35 | 36 | "posts": [{ 37 | // 一份单独的文档 38 | }] 39 | } 40 | ``` 41 | 42 | That document will de-reference to explain your link relations: 43 | 44 | 这份文档将解释链接之间的关系: 45 | 46 | ```text 47 | GET http://api.example.com/profile HTTP/1.1 48 | ``` 49 | 50 | ```text 51 | HTTP/1.1 200 OK 52 | Content-Type: text/plain 53 | ``` 54 | 55 | The Example.com API Profile 56 | =========================== 57 | 58 | Example.com API 使用基于游标的分页。 59 | 它是这样工作的: 60 | 在想要的 `meta` 部分,它将返回一个 `cursors` 的关系(relation), 61 | 其中包括 `after`,`before` 和 `limit`,用来描述该游标。 62 | 您可以使用 `href` 给出的 URI 模板来生成分页的 URIs。 63 | 64 | ```json 65 | "meta": { 66 | "cursors": { 67 | "after": "abcd1234", 68 | "before": "wxyz0987", 69 | "limit": 25, 70 | "href": "https://api.example.com/whatever{?after,before,limit}" 71 | } 72 | } 73 | ``` 74 | -------------------------------------------------------------------------------- /faq/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page 3 | title: FAQ 4 | --- 5 | 6 | ### 为什么 JSON API 还没有发布版? 7 | 8 | 一旦 JSON API 发布稳定版,它将保持向后兼容,它将遵守**永不删除,只是添加**的开发策略。 9 | [#46](https://github.com/json-api/json-api/issues/46) 10 | 11 | ### 为什么不使用 HAL 规范? 12 | 13 | 有几个原因: 14 | 15 | * HAL 递归嵌套子文档,而 JSON API 在顶层采用扁平化对象结构。意味着不同的对象引用相同的 “people”(例如,posts和comments的author)时,这种规范能够保证每个person document仅存在一个有效实例。 16 | 17 | * 相似的,JSON API使用IDs做链接,使从复合响应中缓存文档成为可能,仅当本地不存在对应文档,才会发出后续请求。如果幸运,甚至可以完全无需HTTP请求。 18 | 19 | * HAL是序列化格式,但完全未定义文档更新操作。JSON API则仔细考虑如何更新已存在文档(依赖PATCH和JSON PATCH),以及更新操作与GET请求返回复合文档交互方式。同时定义如何创建,删除文档,以及更新操作的200,204响应。 20 | 21 | 简单来说,JSON API尝试格式化相似的,特殊的client-server通讯接口,使用JSON作为数据交换格式。专注于使用成熟的客户端来调用相关API,客户端能够缓存已经获取到的文档,避免再次请求已缓存信息。 22 | 23 | JSON API从大量实际项目所使用的库中抽象而出。同时定义请求/响应(HAL未定义),以及对应数据交互格式。 24 | 25 | ### 如何获取资源可能的行为? 26 | 你应该使用OPTIONS HTTP方法来获取当前特定资源的行为。OPTIONS请求返回方法的语义遵循JSON API标准。 27 | 28 | 举例来说,如果`"GET,POST"`是URL OPTIONS请求的响应,那么就可以获取该资源信息,以及创建新资源。 29 | 30 | 如果你想知道特定资源属性作用,你不得不使用应用级别的描述来定义属性的含义与功能,并使用错误响应通知用户。这个特性依旧在讨论中,尚未加入最终标准。[discussion](https://github.com/json-api/json-api/issues/7). 31 | 32 | ### 有没有JSON 规范来定义JSON API? 33 | 34 | 当然,你可以在[http://jsonapi.org/schema](http://jsonapi.org/schema)找到JSON规范定义。注意这个规范并不完美。 因为JSON文档可能会通过规范检查,但并不意味着是合适的JSON API文档。规范只是为了常规性排错检查。 35 | 36 | 可以在[http://json-schema.org](http://json-schema.org)找到更多关于JSON 规范格式的信息。 37 | 38 | ### 为什么资源集合作为数组返回,而不是ID索引集合? 39 | 40 | JSON数组是自然排序,而集合需要元数据进行成员排序。因此,默认情况下,数组能够实现更自然的排序或者特殊方式排序。 41 | 42 | 除此之外,JSON API 允许返回不包含IDs的只读资源,与IDs索引集合方式不兼容。 43 | 44 | ### 为什么关联资源嵌套在复合文档的 `linked` 对象中? 45 | 46 | 主要资源应该相互独立,因为他们的顺序和数量通常比较重要。通过多种方式,分离主要资源和关联资源是必要的,因为主要资源可能会有相同类型的关联资源(e.g. the "parents" of a "person")。关联资源嵌套在 `linked` 中能够防止可能的冲突。 47 | -------------------------------------------------------------------------------- /format/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page 3 | title: "格式" 4 | --- 5 | 6 | {% include status.md %} 7 | 8 | ## 介绍 9 | 10 | JSON API 是数据交互规范,用以定义客户端如何获取与修改资源,以及服务器如何响应对应请求。 11 | 12 | JSON API设计用来最小化请求的数量,以及客户端与服务器间传输的数据量。在高效实现的同时,无需牺牲可读性、灵活性和可发现性。 13 | 14 | JSON API需要使用JSON API媒体类型([`application/vnd.api+json`](http://www.iana.org/assignments/media-types/application/vnd.api+json)) 进行数据交互。 15 | 16 | JSON API服务器支持通过GET方法获取资源。而且必须独立实现HTTP POST, PUT和DELETE方法的请求响应,以支持资源的创建、更新和删除。 17 | 18 | JSON API服务器也可以选择性支持HTTP PATCH方法 [[RFC5789](http://tools.ietf.org/html/rfc5789)]和JSON Patch格式 [[RFC6902](http://tools.ietf.org/html/rfc6902)],进行资源修改。JSON Patch支持是可行的,因为理论上来说,JSON API通过单一JSON 文档,反映域下的所有资源,并将JSON文档作为资源操作介质。在文档顶层,依据资源类型分组。每个资源都通过文档下的唯一路径辨识。 19 | 20 | ## 规则约定 21 | 22 | 文档中的关键字, "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", 23 | "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" 依据RFC 2119 24 | [[RFC2119](http://tools.ietf.org/html/rfc2119)]规范解释。 25 | 26 | ##内容约定 27 | 28 | ###客户端职责 29 | 30 | 客户端必须在包含`Content-Type: application/vnd.api+json`头并且不包含媒体类型参数的请求文档中发送所有JSON API数据。 31 | 32 | 在`Accept`头中包含JSON API媒体类型并且不包含媒体类型参数的客户端必须在`Accept`头中指定媒体类型至少一次。 33 | 34 | 客户端必须忽略任何从响应文档的`Content-Type`头中获取的`application/vnd.api+json`媒体类型参数。 35 | 36 | ###服务器职责 37 | 38 | 服务器必须在包含`Content-Type: application/vnd.api+json`头并且不包含媒体类型参数的请求文档中发送所有JSON API数据。 39 | 40 | 如果接收到一个用任何媒体类型参数指定`Content-Type: application/vnd.api+json`头的请求,服务器必须返回一个`415 Unsupported Media Type`状态码响应。 41 | 42 | 如果接收到一个在`Accept`头中包含任何JSON API媒体类型并且所有实体都以媒体类型参数更改的请求,服务器必须返回一个`406 Not Acceptable`状态码响应。 43 | 44 | ## 文档结构 45 | 46 | 这一章节描述JSON API文档结构,通过媒体类型[`application/vnd.api+json`](http://www.iana.org/assignments/media-types/application/vnd.api+json)标示。JSON API文档使用javascript 对象(JSON)[[RFC4627](http://tools.ietf.org/html/rfc4627)]定义。 47 | 48 | 尽管同种媒体类型用以请求和响应文档,但某些特性只适用于其中一种。差异在下面呈现。 49 | 50 | 除非另有说明,根据本规范定义的对象都不应该包含任何其他键。客户端和服务器实现必须忽略本规范未指定的键。 51 | 52 | ### Top Level 53 | 54 | JSON 对象必须位于每个JSON API文档的根级。这个对象定义文档的“top level”。 55 | 56 | 文档必须包含以下至少一种top-level键: 57 | 58 | * `data`: 文档的”primary data”. 59 | * `errors`: [错误对象](http://jsonapi.org/format/#errors)列表. 60 | * `meta`: 包含非标准元信息的[元对象](http://jsonapi.org/format/#document-meta). 61 | 62 | `data`键和`errors`键不能再一个文档中同时存在。 63 | 64 | 文档可能包含以下任何top-level键: 65 | 66 | * `jsonapi`: 描述服务器实现的对象. 67 | * `links`: 与primary data相关的[链接对象](http://jsonapi.org/format/#document-links). 68 | * `include`: 与primary data或其他资源相关的资源对象("included resources")列表. 69 | 70 | 如果文档不包含top-level `data`键,`included`键也不应该出现。 71 | 72 | 文档的top-level [链接对象](http://jsonapi.org/format/#document-links)可能包含以下键: 73 | 74 | * `self`: 生成当前响应文档的[链接](http://jsonapi.org/format/#document-links). 75 | * `related`: 当primary data代表资源关系时,表示[相关资源链接](http://jsonapi.org/format/#document-resource-object-related-resource-links). 76 | * Primary data的[分页](http://jsonapi.org/format/#fetching-pagination)链接. 77 | 78 | 文档中的“primary data”代表一个请求所要求的资源或资源集合。 79 | 80 | Primary data 必须是以下列举的一种: 81 | 82 | * 如果请求要求单一资源,应该是一个单一[资源对象](http://jsonapi.org/format/#document-resource-objects),或一个单一[资源标识符](http://jsonapi.org/format/#document-resource-identifier-objects),或`null`. 83 | * 如果请求要求资源集合,应该是一个[资源对象](http://jsonapi.org/format/#document-resource-objects)列表,或一个空列表(`[]`). 84 | 85 | 例如,以下primary data表示一个单一资源对象: 86 | 87 | ```javascript 88 | { 89 | "data": { 90 | "type": "articles", 91 | "id": "1", 92 | "attributes": { 93 | // ... this article's attributes 94 | }, 95 | "relationships": { 96 | // ... this article's relationships 97 | } 98 | } 99 | } 100 | ``` 101 | 102 | 以下primary data表示一个指向同样资源的单一[资源标识符](http://jsonapi.org/format/#document-resource-identifier-objects): 103 | 104 | ```javascript 105 | { 106 | "data": { 107 | "type": "articles", 108 | "id": "1" 109 | } 110 | } 111 | ``` 112 | 113 | 即使只包含一个元素或为空,资源的一个逻辑集合也必须表示为一个列表。 114 | 115 | ### 资源对象 116 | 117 | JSON API 文档中的"Resuorce objects"代表资源。 118 | 119 | 一个资源对象必须至少包含以下top-level键: 120 | 121 | * `id` 122 | * `type' 123 | 124 | 例外:当资源对象来自客户端并且代表一个将要在服务器创建的新资源时,`id`键不是必须的。 125 | 126 | 此外,资源对象可能包含以下top-level键: 127 | 128 | * 'attribute': [属性对象](http://jsonapi.org/format/#document-resource-object-attributes)代表资源的某些数据. 129 | * `relationshiops`: [关联对象](http://jsonapi.org/format/#document-resource-object-relationships)描述该资源与其他JSON API资源之间的关系. 130 | * `links`: [链接资源](http://jsonapi.org/format/#document-links)包含与资源相关的链接. 131 | * `meta`: [元数据资源](http://jsonapi.org/format/#document-meta)包含与资源对象相关的非标准元信息,这些信息不能被作为属性或关联对象表示. 132 | 133 | 一篇文献(即一个"文献"类型的资源)在文档中这样表示: 134 | 135 | ```javascript 136 | // ... 137 | { 138 | "type": "articles", 139 | "id": "1", 140 | "attributes": { 141 | "title": "Rails is Omakase" 142 | }, 143 | "relationships": { 144 | "author": { 145 | "links": { 146 | "self": "/articles/1/relationships/author", 147 | "related": "/articles/1/author" 148 | }, 149 | "data": { "type": "people", "id": "9" } 150 | } 151 | } 152 | } 153 | // ... 154 | ``` 155 | 156 | #### 标识符 157 | 158 | 每个[资源对象](http://jsonapi.org/format/#document-resource-objects)包含一个`id`键和一个`type`键。`id`键和`type`键的值必须是字符串类型。 159 | 160 | 对于每一个既定API,每个资源对象的`type`和`id`对必须定义一个单独且唯一的资源。(由一个或多个但行为表现为一个服务器的服务器控制的URI集合构成一个API。) 161 | 162 | `type`键用于描述共享相同属性和关联的[资源对象](http://jsonapi.org/format/#document-resource-objects)。 163 | 164 | `type`键的值必须与遵循[键名称](http://jsonapi.org/format/#document-member-names)相同的约束条件。 165 | 166 | ####字段 167 | 168 | 资源对象的[属性](http://jsonapi.org/format/#document-resource-object-attributes)和[关联](http://jsonapi.org/format/#document-resource-object-relationships)被统称为"[fields](http://jsonapi.org/format/#document-resource-object-fields)"。 169 | 170 | 一个[资源对象](http://jsonapi.org/format/#document-resource-objects)的所有字段必须与`type`和`id`在同一命名空间中。即一个资源不能拥有名字相同的属性与关联,也不能拥有被命名为`type`或`id`的属性和关联。 171 | 172 | ####属性 173 | 174 | `attribute`键的值必须是一个对象(一个"attributes object")。属性对象的键("attributes")代表与[资源对象](http://jsonapi.org/format/#document-resource-objects)中定义的与其有关的信息。 175 | 176 | 属性可以包含任何合法JSON值。 177 | 178 | JSON对象和列表涉及的复杂数据结构可以作为属性的值。但是一个组成或被包含于属性中的对象不能包含`relationships`或`links`键,因为这些键为此规范未来的用途所预留。 179 | 180 | 虽然一些has-one关系的外键(例如author_id)被在内部与其他将要在资源对象中表达的信息一起储存,但是这些键不能作为属性出现。 181 | 182 | ####关联 183 | 184 | `relationships`键的值必须是一个对象("relationships object")。 185 | 186 | 关联对象("relationships")的键表示在[资源对象](http://jsonapi.org/format/#document-resource-objects)中定义的与其相关的其他资源对象。 187 | 188 | 关联可以是单对象关联或多对象关联。 189 | 190 | 一个"relationship object"必须包含以下至少一种键: 191 | 192 | * `links`: 一个[链接对象](http://jsonapi.org/format/#document-links)至少包含以下一种键: 193 | + `self`: 指向关联本身的链接("relationship link")。此链接允许客户端直接修改关联。例如,通过一个`articale`的关联URL移除一个`author`将会解除一个人与`article`的关系而不需要删除这个`people`资源本身。获取成功后,这个链接将返回一个相关资源之间的[连接](http://jsonapi.org/format/#document-resource-object-linkage),将其作为primary data。(见 [获取关联](http://jsonapi.org/format/#fetching-relationships)). 194 | + `related`: [相关资源链接](http://jsonapi.org/format/#document-resource-object-related-resource-links). 195 | * `data`: [资源连接](http://jsonapi.org/format/#document-resource-object-linkage). 196 | * `meta`: 包含关于此关联的非标准元信息的[元对象](http://jsonapi.org/format/#document-meta). 197 | 198 | 一个代表多对象关联的关联对象可能在`links`键下也包含[分页](http://jsonapi.org/format/#fetching-pagination)链接,如后文描述。 199 | 200 | ####相关资源链接 201 | 202 | "related resource link"提供 [关联](http://jsonapi.org/format/#document-resource-object-relationships)中[链接](http://jsonapi.org/format/#document-links)的[资源对象](http://jsonapi.org/format/#document-resource-objects)的权限。当获取成功后,相关资源对象将作为响应的primary data返回。 203 | 204 | 例如,通过一个`GET`请求检索时,一篇`article`的`comments`关联可以指定一个返回评论集合[资源对象](http://jsonapi.org/format/#document-resource-objects)的[链接](http://jsonapi.org/format/#document-links)。 205 | 206 | 当一个相关资源链接出现时,它必须指向一个合法URL, 即使关联当前不予任何目标资源相关。此外,一个相关资源链接不能因为它的关联内容更改而更改。 207 | 208 | ####资源连接 209 | 210 | [复合文档](http://jsonapi.org/format/#document-compound-documents)中的资源连接允许客户端将所有包含的[资源对象](http://jsonapi.org/format/#document-resource-objects)链接在一起,而不需要通过[链接](http://jsonapi.org/format/#document-links)`GET`任何URL。 211 | 212 | 资源连接必须通过以下一种形式表述: 213 | 214 | * 空的单对象关联用`null`表示. 215 | * 空的多对象关联用一个空列表(`[]`)表示. 216 | * 非空单对象关联用一个[资源标识符对象](http://jsonapi.org/format/#document-resource-identifier-objects)表示. 217 | * 非空多对象关联用一个[资源标识符对象](http://jsonapi.org/format/#document-resource-identifier-objects)列表表示. 218 | 219 | 例如,以下文献与`author`相关: 220 | 221 | ```javascripts 222 | // ... 223 | { 224 | "type": "articles", 225 | "id": "1", 226 | "attributes": { 227 | "title": "Rails is Omakase" 228 | }, 229 | "relationships": { 230 | "author": { 231 | "links": { 232 | "self": "http://example.com/articles/1/relationships/author", 233 | "related": "http://example.com/articles/1/author" 234 | }, 235 | "data": { "type": "people", "id": "9" } 236 | } 237 | }, 238 | "links": { 239 | "self": "http://example.com/articles/1" 240 | } 241 | } 242 | // ... 243 | ``` 244 | 245 | `author`关联包含一个指向关联本身的链接(这允许客户端直接更改相关作者)、一个用于获取资源对象的相关资源链接和一个连接信息。 246 | 247 | ####资源链接 248 | 249 | [资源对象](http://jsonapi.org/format/#document-resource-objects)中的可选的`links`键包含与资源相关的[链接](http://jsonapi.org/format/#document-links)。 250 | 251 | 当链接对象存在时,它可能包含指向定义了资源对象所表示的资源的`self`[链接](http://jsonapi.org/format/#document-links)。 252 | 253 | ```javascript 254 | // ... 255 | { 256 | "type": "articles", 257 | "id": "1", 258 | "attributes": { 259 | "title": "Rails is Omakase" 260 | }, 261 | "links": { 262 | "self": "http://example.com/articles/1" 263 | } 264 | } 265 | // ... 266 | ``` 267 | 268 | 服务器收到一个特定URL的`GET`请求后,必须回复将此资源作为promary data的响应。 269 | 270 | ###资源标识符对象 271 | 272 | “resource identifier object”是一个定义单独资源的对象。 273 | 274 | “resource identifier object”必须包含`type`和`id`键。 275 | 276 | “resource identifier object”也可能包含`meta`键,它的值是包含非标准元信息的[meta](http://jsonapi.org/format/#document-meta)对象。 277 | 278 | ###复合文档 279 | 280 | 为了减少HTTP请求的数量,服务器可能允许包含相关资源与所请求的主要资源的响应。这种响应被称作"compound documents"。 281 | 282 | 在复合文档中,所有包含的资源必须以一个top-level中的`included`键中的[资源对象](http://jsonapi.org/format/#document-resource-objects)列表表示。 283 | 284 | 复合文档要求 “full linkage”,这意味着所有包含其中的资源必须至少被一个在同一文档中的[资源标识符对象](http://jsonapi.org/format/#document-resource-identifier-objects)定义。这些资源标识符对象可以是primary data或与主要或相关资源包含在一起的资源连接。 285 | 286 | 全连接要求唯一的例外是当包含连接数据的关联字段被通过稀疏字段集合排除时。 287 | 288 | 一个含有多个包含关联的复杂示例文档如下所示: 289 | 290 | ```javascript 291 | { 292 | "data": [{ 293 | "type": "articles", 294 | "id": "1", 295 | "attributes": { 296 | "title": "JSON API paints my bikeshed!" 297 | }, 298 | "links": { 299 | "self": "http://example.com/articles/1" 300 | }, 301 | "relationships": { 302 | "author": { 303 | "links": { 304 | "self": "http://example.com/articles/1/relationships/author", 305 | "related": "http://example.com/articles/1/author" 306 | }, 307 | "data": { "type": "people", "id": "9" } 308 | }, 309 | "comments": { 310 | "links": { 311 | "self": "http://example.com/articles/1/relationships/comments", 312 | "related": "http://example.com/articles/1/comments" 313 | }, 314 | "data": [ 315 | { "type": "comments", "id": "5" }, 316 | { "type": "comments", "id": "12" } 317 | ] 318 | } 319 | } 320 | }], 321 | "included": [{ 322 | "type": "people", 323 | "id": "9", 324 | "attributes": { 325 | "first-name": "Dan", 326 | "last-name": "Gebhardt", 327 | "twitter": "dgeb" 328 | }, 329 | "links": { 330 | "self": "http://example.com/people/9" 331 | } 332 | }, { 333 | "type": "comments", 334 | "id": "5", 335 | "attributes": { 336 | "body": "First!" 337 | }, 338 | "relationships": { 339 | "author": { 340 | "data": { "type": "people", "id": "2" } 341 | } 342 | }, 343 | "links": { 344 | "self": "http://example.com/comments/5" 345 | } 346 | }, { 347 | "type": "comments", 348 | "id": "12", 349 | "attributes": { 350 | "body": "I like XML better" 351 | }, 352 | "relationships": { 353 | "author": { 354 | "data": { "type": "people", "id": "9" } 355 | } 356 | }, 357 | "links": { 358 | "self": "http://example.com/comments/12" 359 | } 360 | }] 361 | } 362 | ``` 363 | 364 | 对于每个`typy`和`id`对,一个[复合文档](http://jsonapi.org/format/#document-compound-documents)不能包含超过一个[资源对象](http://jsonapi.org/format/#document-resource-objects)。 365 | 366 | ###元信息 367 | 368 | 在指定位置,`meta`键可用于包含非标准元信息。每个`meta`键的值必须是一个对象("meta object")。 369 | 370 | 任何键都可以包含在`meta`对象中。 371 | 372 | 例如: 373 | 374 | ```javascript 375 | { 376 | "meta": { 377 | "copyright": "Copyright 2015 Example Corp.", 378 | "authors": [ 379 | "Yehuda Katz", 380 | "Steve Klabnik", 381 | "Dan Gebhardt", 382 | "Tyler Kellen" 383 | ] 384 | }, 385 | "data": { 386 | // ... 387 | } 388 | } 389 | ``` 390 | 391 | ###链接 392 | 393 | 在指定位置,`links`键可用于表示链接。每个`links`键的值必须是一个对象("links object")。 394 | 395 | 每个链接对象的键是一个"link"。一个链接必须是以下形其中之一: 396 | 397 | * 一个包含链接的URL的字符串. 398 | * 一个可以包含以下键的对象("link object"): 399 | - ​`href`: 一个包含此链接URL的字符串 400 | - `meta`: 包含与此链接相关非标准元信息的元对象. 401 | 402 | 以下`self`链接是一个简单的URL: 403 | 404 | ```javascript 405 | "links": { 406 | "self": "http://example.com/posts" 407 | } 408 | ``` 409 | 410 | 以下`related`链接包含一个URL和与资源集合相关的元信息: 411 | 412 | ```javascript 413 | "links": { 414 | "related": { 415 | "href": "http://example.com/articles/1/comments", 416 | "meta": { 417 | "count": 10 418 | } 419 | } 420 | } 421 | ``` 422 | 423 | ###JSON API 对象 424 | 425 | 一个JSON API文档可能在top level `jsonapi`键下包含与其实现相关的信息。如果它出现,`jsonapi`键的值必须是一个对象("jsonapi object")。jsonapi对象可能包含`version`键,其值是一个表明被支持的最高JSON API版本的字符串。此对象也可能包含一个`meta`键,其值是一个包含非标准元信息的[meta](http://jsonapi.org/format/#document-meta)对象。 426 | 427 | ```javascript 428 | { 429 | "jsonapi": { 430 | "version": "1.0" 431 | } 432 | } 433 | ``` 434 | 435 | 如果`version`键未出现,客户端则假设服务器是遵循此规范最新版本1.0。 436 | 437 | ####键名称 438 | 439 | 一个JSON API文档中使用的键名称对于客户端和服务器来说必须是大小写敏感的,并且必须满足以下规则: 440 | 441 | * 键名称必须包含至少一个字符. 442 | * 键名称必须只包含以下列举的合法字符. 443 | * 键名称必须以以下定义的“globally allowed character”开始和结束. 444 | 445 | 为了确保键名称能便利地映射到URL,推荐使用[RFC 3986](https://tools.ietf.org/html/rfc3986#page-13)中指定的非保留URL安全字符。 446 | 447 | ####合法字符 448 | 449 | 以下“globally allowed characters”可以被用于键名称中: 450 | 451 | * U+0061 to U+007A, “a-z” 452 | * U+0041 to U+005A, “A-Z” 453 | * U+0030 to U+0039, “0-9” 454 | * U+0080 and above (非ASCII Unicode 字符; 非URL安全字符,不推荐) 455 | 456 | 此外,以下字符也在键名称中也是合法的,只是不能出现在首位或末位: 457 | 458 | * U+002D HYPHEN-MINUS, “-“ 459 | * U+005F LOW LINE, “_” 460 | * U+0020 SPACE, “ “ (非URL安全字符,不推荐) 461 | 462 | ####保留字符 463 | 464 | 以下字符不能被用于键名称中: 465 | 466 | * U+002B PLUS SIGN, “+” (用于排序) 467 | * U+002C COMMA, “,” (用于关联路径的分隔符) 468 | * U+002E PERIOD, “.” (用于关联路径中的分隔符) 469 | * U+005B LEFT SQUARE BRACKET, “[” (用于稀疏字段集合) 470 | * U+005D RIGHT SQUARE BRACKET, “]” (用于稀疏字段集合) 471 | * U+0021 EXCLAMATION MARK, “!” 472 | * U+0022 QUOTATION MARK, ‘”’ 473 | * U+0023 NUMBER SIGN, “#” 474 | * U+0024 DOLLAR SIGN, “$” 475 | * U+0025 PERCENT SIGN, “%” 476 | * U+0026 AMPERSAND, “&” 477 | * U+0027 APOSTROPHE, “’” 478 | * U+0028 LEFT PARENTHESIS, “(“ 479 | * U+0029 RIGHT PARENTHESIS, “)” 480 | * U+002A ASTERISK, “*” 481 | * U+002F SOLIDUS, “/” 482 | * U+003A COLON, “:” 483 | * U+003B SEMICOLON, “;” 484 | * U+003C LESS-THAN SIGN, “<” 485 | * U+003D EQUALS SIGN, “=” 486 | * U+003E GREATER-THAN SIGN, “>” 487 | * U+003F QUESTION MARK, “?” 488 | * U+0040 COMMERCIAL AT, “@” 489 | * U+005C REVERSE SOLIDUS, “" 490 | * U+005E CIRCUMFLEX ACCENT, “^” 491 | * U+0060 GRAVE ACCENT, “`” 492 | * U+007B LEFT CURLY BRACKET, “{“ 493 | * U+007C VERTICAL LINE, “” 494 | * U+007D RIGHT CURLY BRACKET, “}” 495 | * U+007E TILDE, “~” 496 | * U+007F DELETE 497 | * U+0000 to U+001F (C0 Controls) 498 | 499 | ## 数据获取 500 | 501 | 数据包括资源和关联,可以通过向后端发送`GET`请求获取数据。 502 | 503 | 响应可以通过以下描述的可选特征进一步提取。 504 | 505 | ### 资源获取 506 | 507 | 服务器必须支持通过URL获取资源,URL可以通过以下形式提供: 508 | 509 | * top-level链接对象中的`self`链接. 510 | * resource-level链接对象中的`self`链接. 511 | * relationship-level链接对象中的`related`链接. 512 | 513 | 例如,以下请求获取一个文献集合: 514 | 515 | ```javascript 516 | GET /articles HTTP/1.1 517 | Accept: application/vnd.api+json 518 | ``` 519 | 520 | 以下请求获取一篇文献: 521 | 522 | ```javascript 523 | GET /articles/1 HTTP/1.1 524 | Accept: application/vnd.api+json 525 | ``` 526 | 527 | 以下请求获取一篇文献的作者: 528 | 529 | ```javascript 530 | GET /articles/1/author HTTP/1.1 531 | Accept: application/vnd.api+json 532 | ``` 533 | 534 | #### 响应 535 | 536 | ##### 200 OK 状态码 537 | 538 | 如果服务器通过请求成功获取一个单独资源或资源集合,服务器必须以`200 OK`状态码回复。 539 | 540 | 如果服务器通过请求成功获取一个资源集合,服务器必须将一个[资源对象](http://jsonapi.org/format/#document-resource-objects)的列表或空列表(`[]`)作为响应文档的主数据。 541 | 542 | 例如,对于一个针对文献集合的`GET`请求可以回复: 543 | 544 | ```javascript 545 | HTTP/1.1 200 OK 546 | Content-Type: application/vnd.api+json 547 | 548 | { 549 | "links": { 550 | "self": "http://example.com/articles" 551 | }, 552 | "data": [{ 553 | "type": "articles", 554 | "id": "1", 555 | "attributes": { 556 | "title": "JSON API paints my bikeshed!" 557 | } 558 | }, { 559 | "type": "articles", 560 | "id": "2", 561 | "attributes": { 562 | "title": "Rails is Omakase" 563 | } 564 | }] 565 | } 566 | ``` 567 | 568 | 一个空集合的相似回复可以是以下形式: 569 | 570 | ```javascript 571 | HTTP/1.1 200 OK 572 | Content-Type: application/vnd.api+json 573 | 574 | { 575 | "links": { 576 | "self": "http://example.com/articles" 577 | }, 578 | "data": [] 579 | } 580 | ``` 581 | 582 | 如果服务器可以通过请求成功获取一个单独资源,服服务器必须将一个[资源对象](http://jsonapi.org/format/#document-resource-objects)或`null`作为响应文档的主数据。 583 | 584 | 只有当请求URL可能指向、但当前并不指向一个单独资源时,`null`才应当作为响应。 585 | 586 | 例如,一个针对一篇文献的请求可以通过以下形式回复: 587 | 588 | ```javascript 589 | HTTP/1.1 200 OK 590 | Content-Type: application/vnd.api+json 591 | 592 | { 593 | "links": { 594 | "self": "http://example.com/articles/1" 595 | }, 596 | "data": { 597 | "type": "articles", 598 | "id": "1", 599 | "attributes": { 600 | "title": "JSON API paints my bikeshed!" 601 | }, 602 | "relationships": { 603 | "author": { 604 | "links": { 605 | "related": "http://example.com/articles/1/author" 606 | } 607 | } 608 | } 609 | } 610 | } 611 | ``` 612 | 613 | 如果以上文献的作者是缺失的,相关资源的`GET`请求可以通过以下形式回复: 614 | 615 | ```javasript 616 | HTTP/1.1 200 OK 617 | Content-Type: application/vnd.api+json 618 | 619 | { 620 | "links": { 621 | "self": "http://example.com/articles/1/author" 622 | }, 623 | "data": null 624 | } 625 | ``` 626 | 627 | ##### 404 Not Found 状态码 628 | 629 | 当通过一个请求获取一个不存在的单独资源时,服务器必须以`404 Not Found`状态码回复,除非请求允许`null`作为`200 OK`状态码响应的主数据(见后文描述)。 630 | 631 | ##### 其他响应 632 | 633 | 服务器可以回复其他HTTP状态码。 634 | 635 | 服务器可以在错误响应中包含[错误详情](http://jsonapi.org/format/#errors)。 636 | 637 | 服务器和客户端必须依据[HTTP语义](https://tools.ietf.org/html/rfc7231)分别生成和解析响应。 638 | 639 | ### 关联获取 640 | 641 | 服务器必须支持通过关联URL获取关联数据,关联URL可以通过关联的`links`对象中的`self`链接提供。 642 | 643 | 例如,以下请求获取文献的评论数据: 644 | 645 | ```javascript 646 | GET /articles/1/relationships/comments HTTP/1.1 647 | Accept: application/vnd.api+json 648 | ``` 649 | 650 | 以下请求获取文献的作者数据: 651 | 652 | ```javascript 653 | GET /articles/1/relationships/author HTTP/1.1 654 | Accept: application/vnd.api+json 655 | ``` 656 | 657 | #### 响应 658 | 659 | ##### 200 OK 状态码 660 | 661 | 当服务器通过请求成功获取关联时,服务器应该以`200 OK`状态码回复。 662 | 663 | 响应文档的主数据必须是与[资源连接](http://jsonapi.org/format/#document-resource-object-linkage)相匹配的值,如上文对[关联对象](http://jsonapi.org/format/#document-resource-object-relationships)的描述。 664 | 665 | Top-level[链接对象](http://jsonapi.org/format/#document-links)可以包含`self`和`related`链接,如上文对[关联对象](http://jsonapi.org/format/#document-resource-object-relationships)的描述。 666 | 667 | 例如,一个来自单对象关联链接的`GET`请求可以通过以下形式回复: 668 | 669 | ```javascript 670 | HTTP/1.1 200 OK 671 | Content-Type: application/vnd.api+json 672 | 673 | { 674 | "links": { 675 | "self": "/articles/1/relationships/author", 676 | "related": "/articles/1/author" 677 | }, 678 | "data": { 679 | "type": "people", 680 | "id": "12" 681 | } 682 | } 683 | ``` 684 | 685 | 如果以上关联为空,相同URL的`GET`请求可以通过以下形式回复: 686 | 687 | ```javascript 688 | HTTP/1.1 200 OK 689 | Content-Type: application/vnd.api+json 690 | 691 | { 692 | "links": { 693 | "self": "/articles/1/relationships/author", 694 | "related": "/articles/1/author" 695 | }, 696 | "data": null 697 | } 698 | ``` 699 | 700 | 一个来自多对象关联链接的`GET`请求可以通过以下形式回复: 701 | 702 | ```javascript 703 | HTTP/1.1 200 OK 704 | Content-Type: application/vnd.api+json 705 | 706 | { 707 | "links": { 708 | "self": "/articles/1/relationships/tags", 709 | "related": "/articles/1/tags" 710 | }, 711 | "data": [ 712 | { "type": "tags", "id": "2" }, 713 | { "type": "tags", "id": "3" } 714 | ] 715 | } 716 | ``` 717 | 718 | 如果以上关联为空,相同URL的`GET`请求可以通过以下形式回复: 719 | 720 | ```javascript 721 | HTTP/1.1 200 OK 722 | Content-Type: application/vnd.api+json 723 | 724 | { 725 | "links": { 726 | "self": "/articles/1/relationships/tags", 727 | "related": "/articles/1/tags" 728 | }, 729 | "data": [] 730 | } 731 | ``` 732 | 733 | ##### 404 Not Found 状态码 734 | 735 | 当通过一个请求获取一个不存在的关联链接URL时,服务器必须以`404 Not Found`状态码回复。 736 | 737 | 如果关联链接URL存在但是关联为空,如前文所述,服务器必须以`200 OK`状态码回复。 738 | 739 | ##### 其他响应 740 | 741 | 服务器可以回复其他HTTP状态码。 742 | 743 | 服务器可以在错误响应中包含[错误详情](http://jsonapi.org/format/#errors)。 744 | 745 | 服务器和客户端必须依据[HTTP语义](https://tools.ietf.org/html/rfc7231)分别生成和解析响应。 746 | 747 | ### 内联资源 748 | 749 | 后端可以默认回复与主数据相关的资源。 750 | 751 | 后端也可以支持`include`请求参数以保证客户端可以指定需要回复的相关资源。 752 | 753 | 如果后端不支持`include`参数,必须以`400 Bad Request`状态码回复任何包含此参数的请求。 754 | 755 | 如果后端支持`include`参数并且客户端使用了此参数,服务器不能在[复合文档](http://jsonapi.org/format/#document-compound-documents)的`included`部分包含任何未请求的[资源对象](http://jsonapi.org/format/#document-resource-objects)。 756 | 757 | `include`参数的值必须是一个由逗号分隔符(U+002C COMMA, “,”) 分割的关联路径列表。关联路径是由点号分隔符(U+002E FULL-STOP, “.”) 分割的[关联](http://jsonapi.org/format/#document-resource-object-relationships)名称。 758 | 759 | 如果服务器不能识别关联路径或不能通过路径支持内联资源,必须以`400 Bad Request`状态码回复。 760 | 761 | 例如,可以同时请求评论和文献: 762 | 763 | ```javascript 764 | GET /articles/1?include=comments HTTP/1.1 765 | Accept: application/vnd.api+json 766 | ``` 767 | 768 | 可以通过如下形式为每个关联名称定义逗号分割路径,以请求与其他资源相关的资源: 769 | 770 | ```javascript 771 | GET /articles/1?include=comments.author HTTP/1.1 772 | Accept: application/vnd.api+json 773 | ``` 774 | 775 | 可以通过逗号分割列表请求多个相关资源: 776 | 777 | ```javascript 778 | GET /articles/1?include=author,comments.author HTTP/1.1 779 | Accept: application/vnd.api+json 780 | ``` 781 | 782 | 此外,可以向关联后端请求相关资源: 783 | 784 | ```javascript 785 | GET /articles/1/relationships/comments?include=comments.author HTTP/1.1 786 | Accept: application/vnd.api+json 787 | ``` 788 | 789 | 在这种情况下,主数据应该是代表此文献的评论连接的[资源标识符对象](http://jsonapi.org/format/#document-resource-identifier-objects)集合,所有评论和评论作者将会作为相关数据返回。 790 | 791 | ### 稀疏字段组 792 | 793 | 客户端可能通过`fields[TYPE]`参数,请求后端返回只包含特定字段的响应。 794 | 795 | `fields`参数的值必须用逗号分隔开,用来表示需要返回字段的名称。 796 | 797 | 如果客户端请求了一组给定类型的字段,那么后端不能包括资源对象内此类型的附加字段。 798 | 799 | ```javascript 800 | GET /articles?include=author&fields[articles]=title,body&fields[people]=name HTTP/1.1 801 | Accept: application/vnd.api+json 802 | ``` 803 | ### 排序 804 | 805 | 服务器可以选择性支持,根据一个或多个条件(排序字段)对资源集合排序。 806 | 807 | 后端可能支持带有`sort`查询参数的请求,来排序主要数据。`sort`的值必须代表排序字段。 808 | 809 | ```javascript 810 | GET /people?sort=age HTTP/1.1 811 | Accept: application/vnd.api+json 812 | ``` 813 | 814 | 后端可能支持带有多个排序字段的请求,允许用逗号分隔排序字段。排序字段应该被按照特定顺序执行。 815 | 816 | ```javascript 817 | GET /people?sort=age,name HTTP/1.1 818 | Accept: application/vnd.api+json 819 | ``` 820 | 821 | 每个排序字段必须按升序排列。除非它带有`-`前缀,这种情况下将按降序排列。 822 | 823 | ```javascript 824 | GET /articles?sort=-created,title HTTP/1.1 825 | Accept: application/vnd.api+json 826 | ``` 827 | 828 | 以上的例子应首先返回最新的条目。在同一天创建的条目讲按照标题的字母升序排列。 829 | 830 | 如果服务器不支持查询参数`sort`指定的排序,必须返回`400 Bad Request`。 831 | 832 | 如果服务器支持排序,并且客户端通过查询参数`sort`进行排序,服务器必须返回根据指定条件排序的, 833 | 上层`data`数组的元素。 834 | 835 | ### 分页 836 | 837 | 服务器可以选择性限制响应返回的资源数量,为所有可获取资源的子集("page")。 838 | 839 | 服务器可以提供传送分页后数据集的连接("pagination links")。 840 | 841 | `pagination links`必须出现在集合相关的连接对象中。需要在上层`links`对象中提供`pagination links`。 842 | 843 | 下面的键必须被用于`pagination links`: 844 | * `first`: 第一页数据 845 | * `last`: 最后一页数据 846 | * `prev`: 前一页数据 847 | * `next`: 后一页数据 848 | 849 | 如需表示特定的连接不可用,这些键必须被省略,或者值为`null`。 850 | 851 | 分页的排序,必须与JSON API的排序规则一致。 852 | 853 | 查询参数`page`是分页的保留字,服务器和客户端应该用这个参数进行分页。 854 | 855 | 856 | ### 过滤 857 | 858 | 查询参数`filter`是过滤的保留字,服务器和客户端应该用这个参数进行过滤。 859 | 860 | 861 | ## 创建,更新,删除资源 862 | 863 | 服务器可能支持给定类型资源的创建,服务器也可能允许已存在资源的更新和删除。 864 | 865 | 服务器允许单次请求,更新多个资源,如下所述。多个资源更新必须完全成功或者失败,不允许部分更新成功。 866 | 867 | 任何包含内容的请求,必须包含`Content-Type:application/vnd.api+json`请求头。 868 | 869 | ### 创建资源 870 | 871 | 872 | 向表示待创建资源所属资源集的URL,发出`POST`请求,创建一个或多个资源。 873 | 请求必须包含单一资源对象作为主数据。资源对象必须包含至少一个`type`成员。 874 | 875 | 例如,新photo可以通过如下请求创建: 876 | 877 | ```javascript 878 | POST /photos HTTP/1.1 879 | Content-Type: application/vnd.api+json 880 | Accept: application/vnd.api+json 881 | 882 | { 883 | "data": { 884 | "type": "photos", 885 | "attributes": { 886 | "title": "Ember Hamster", 887 | "src": "http://example.com/images/productivity.png" 888 | }, 889 | "relationships": { 890 | "photographer": { 891 | "data": { "type": "people", "id": "9" } 892 | } 893 | } 894 | } 895 | } 896 | ``` 897 | 如果资源对象在`relationships`提供了关系,它的值必须是一个有`data`成员的关系对象。 898 | 这个键的值代表新资源将要有的连接。 899 | 900 | #### 客户端生成的ID 901 | 902 | 服务器可能接受创建资源的请求中有客户端生成的ID。ID必须被`id`键指定,它的值必须是通用唯一识别码。 903 | 客户端应该使用 RGC4122 [`RGC4122`](http://tools.ietf.org/html/draft-ietf- 904 | httpbis-p2-semantics-22#section-6.3)中描述的合适的UUID。 905 | 906 | 比如: 907 | 908 | ```javascript 909 | POST /photos HTTP/1.1 910 | Content-Type: application/vnd.api+json 911 | Accept: application/vnd.api+json 912 | 913 | { 914 | "data": { 915 | "type": "photos", 916 | "id": "550e8400-e29b-41d4-a716-446655440000", 917 | "attributes": { 918 | "title": "Ember Hamster", 919 | "src": "http://example.com/images/productivity.png" 920 | } 921 | } 922 | } 923 | ``` 924 | 925 | 服务器必须对不支持的带有客户端生成ID的创建请求返回`403 Forbidden`。 926 | 927 | 928 | #### 响应 929 | 930 | ##### 201 Created 931 | 932 | 如果`POST`请求不包括客户端生成的ID,并且请求的资源成功被创建,服务器必须返回`201 Created`状态码。 933 | 934 | 响应应该包含`Location`头,用以标示请求创建所有资源的位置。 935 | 936 | 响应必须含有一个文档,用以存储所创建的主要资源。 937 | 938 | 如果响应返回的资源对象在`links`成员里包含`self`键,并且响应数据头提供了`Location`, 939 | 那么`self`的值必须匹配`Location`的值。 940 | 941 | ```javascript 942 | HTTP/1.1 201 Created 943 | Location: http://example.com/photos/550e8400-e29b-41d4-a716-446655440000 944 | Content-Type: application/vnd.api+json 945 | 946 | { 947 | "data": { 948 | "type": "photos", 949 | "id": "550e8400-e29b-41d4-a716-446655440000", 950 | "attributes": { 951 | "title": "Ember Hamster", 952 | "src": "http://example.com/images/productivity.png" 953 | }, 954 | "links": { 955 | "self": "http://example.com/photos/550e8400-e29b-41d4-a716-446655440000" 956 | } 957 | } 958 | } 959 | ``` 960 | 961 | ##### 202 Accepted 962 | 963 | 如果创建资源的请求被接受处理,但在服务器响应时处理并未完成,那么服务器必须返回`202 Accepted`状态码。 964 | 965 | ##### 204 No Content 966 | 967 | 如果`POST`请求包括客户端生成的ID,并且请求的资源成功被创建,那么服务器必须返回`201 Created`状态码和响应文档(如上所述), 968 | ,或者只返回`204 No Content`状态码,没有响应文档。 969 | 970 | ##### 403 Forbidden 971 | 972 | 服务器可能向不支持的创建资源的请求返回`403 Forbidden`的响应。 973 | 974 | ##### 404 Not Found 975 | 976 | 如果创建请求引用的相关资源不存在,服务器必须返回`404 Not Found`的响应。 977 | 978 | ##### 409 Conflict 979 | 980 | 如果创建请求中,客户端生成的ID已经存在,服务器必须返回`409 Conflict`的响应。 981 | 982 | 如果创建请求中,资源对象的`type`不在后端支持的类型里,服务器必须返回`409 Conflict`的响应。 983 | 984 | 服务器应该在响应中包括错误详情和足够的信息以识别冲突原因。 985 | 986 | ##### Other Responses 987 | 988 | 服务器响应可能没有状态码。 989 | 990 | 服务器可能响应包括错误详情的错误响应。 991 | 992 | 服务器与客户端必须依照HTTP的语义准备和解译响应。 993 | 994 | 995 | ### 更新资源 996 | 997 | 向表示资源的URL发出`PATCH`请求,即可进行资源更新。 998 | 999 | 资源的URL可以从资源对象的`self`的值获得。或者,当`GET`请求返回了一个资源对象作为主资源, 1000 | 同样的请求URL可被用来更新资源。 1001 | 1002 | `PATCH`请求必须包括一个资源对象作为主资源。资源对象必须包含`type`和`id`成员。 1003 | 1004 | 比如: 1005 | 1006 | ```javascript 1007 | PATCH /articles/1 HTTP/1.1 1008 | Content-Type: application/vnd.api+json 1009 | Accept: application/vnd.api+json 1010 | 1011 | { 1012 | "data": { 1013 | "type": "articles", 1014 | "id": "1", 1015 | "attributes": { 1016 | "title": "To TDD or Not" 1017 | } 1018 | } 1019 | } 1020 | ``` 1021 | 1022 | #### 更新资源属性 1023 | 1024 | 资源的任何一个,或者所有属性,可能被包括在`PATCH`请求的资源对象中。 1025 | 1026 | 如果请求不包括资源所有的属性,那么服务器解译请求时必须添加这些属性,并赋予当前的值。 1027 | 服务器不能给缺失的属性赋值为null。 1028 | 1029 | 比如,下面的`PATCH`请求被解译为只更新`title`和`text`两个属性: 1030 | 1031 | ```javascript 1032 | PATCH /articles/1 HTTP/1.1 1033 | Content-Type: application/vnd.api+json 1034 | Accept: application/vnd.api+json 1035 | 1036 | { 1037 | "data": { 1038 | "type": "articles", 1039 | "id": "1", 1040 | "attributes": { 1041 | "title": "To TDD or Not", 1042 | "text": "TLDR; It's complicated... but check your test coverage regardless." 1043 | } 1044 | } 1045 | } 1046 | ``` 1047 | 1048 | #### 更新资源关联 1049 | 1050 | 资源的所有或者任何一个关联都可能包括在`PATCH`请求的资源对象中。 1051 | 1052 | 如果一个请求不包括资源所有的关联,那么服务器解译请求时必须添加这些属性,并赋予当前的值。 1053 | 服务器不能给缺失的属性赋值为null或者为空。 1054 | 1055 | 如果关联是被`PATCH`请求的资源对象中`relationships`给出的,那么它的值必须是一个有`data`成员的关联对象。 1056 | 关联的值将被这个成员定义的值代替。 1057 | 1058 | 比如,下面的`PATCH`请求将会更新`author`关联: 1059 | 1060 | ```javascript 1061 | PATCH /articles/1 HTTP/1.1 1062 | Content-Type: application/vnd.api+json 1063 | Accept: application/vnd.api+json 1064 | 1065 | { 1066 | "data": { 1067 | "type": "articles", 1068 | "id": "1", 1069 | "relationships": { 1070 | "author": { 1071 | "data": { "type": "people", "id": "1" } 1072 | } 1073 | } 1074 | } 1075 | } 1076 | ``` 1077 | 1078 | 相似的,下面的`PATCH`请求会更新整个`tags`关联: 1079 | 1080 | ```javascript 1081 | PATCH /articles/1 HTTP/1.1 1082 | Content-Type: application/vnd.api+json 1083 | Accept: application/vnd.api+json 1084 | 1085 | { 1086 | "data": { 1087 | "type": "articles", 1088 | "id": "1", 1089 | "relationships": { 1090 | "tags": { 1091 | "data": [ 1092 | { "type": "tags", "id": "2" }, 1093 | { "type": "tags", "id": "3" } 1094 | ] 1095 | } 1096 | } 1097 | } 1098 | } 1099 | ``` 1100 | 1101 | 服务器可能拒绝更新多对象关联的请求。在这种情况,服务器必须阻止整个更新,并返回`403 Forbidden`状态码。 1102 | 1103 | ### 响应 1104 | 1105 | #### 202 Accepted 1106 | 1107 | 如果服务器接受更新,但在服务器响应时更新并未完成,那么服务器必须返回`202 Accepted`状态码。 1108 | 1109 | #### 204 No Content 1110 | 1111 | 如果更新成功,且服务器没有更新任何未提供的属性,那么服务器必须或者返回`204 No Content`状态码和响应文档,或者只返回`204 No Content`状态码,没有响应文档。 1112 | 1113 | #### 200 OK 1114 | 1115 | 如果服务器接受更新,但是在请求指定内容之外做了资源修改,必须响应`200 OK`以及更新的资源实例,像是向此URL发出`GET`请求。 1116 | 1117 | 如果更新成功,客户端当前属性保持更新,并且服务器只响应最上层元数据,那么服务器必须返回`200 OK`状态码。 1118 | 这种情况下,服务器不能包括更新后的资源。 1119 | 1120 | #### 其它响应 1121 | 1122 | 服务器使用其它HTTP错误状态码反映错误。 1123 | 客户端必须依据HTTP规范处理这些错误信息。 1124 | 如下所述,错误细节可能会一并返回。 1125 | 1126 | ### 更新关联 1127 | 1128 | 虽然关联可以通过资源被更新(如上所述),JSON API也支持单独通过`Relationship Links`更新关联。 1129 | 1130 | #### 更新To-One 关联 1131 | 1132 | `PATCH`请求必须包括上层名为`data`的成员,包括: 1133 | 1134 | 相关新资源的资源表示对象 1135 | 1136 | 或者 1137 | 1138 | `null`,用来删除关联。 1139 | 1140 | 比如,下面的请求更新author关联: 1141 | 1142 | ```javascript 1143 | PATCH /articles/1/relationships/author HTTP/1.1 1144 | Content-Type: application/vnd.api+json 1145 | Accept: application/vnd.api+json 1146 | 1147 | { 1148 | "data": { "type": "people", "id": "12" } 1149 | } 1150 | ``` 1151 | 1152 | 而下面的请求会清除author关联: 1153 | 1154 | ```javascript 1155 | PATCH /articles/1/relationships/author HTTP/1.1 1156 | Content-Type: application/vnd.api+json 1157 | Accept: application/vnd.api+json 1158 | 1159 | { 1160 | "data": null 1161 | } 1162 | ``` 1163 | 1164 | 如果关联被成功更新,服务器必须返回一个成功的响应。 1165 | 1166 | #### 更新To-Many 关联 1167 | 1168 | 对所有请求类型,主体必须包括一个`data`成员,其值是一个空数组,或是一个`资源标识对象`的数组。 1169 | 1170 | 如果客户端向一个to-many关联连接的URL发出`PATCH`请求,服务器必须或者完全更改关联的每一个成员, 1171 | 如果资源不存在或者无法使用,返回合适的错误状态码,如果服务器不允许完全更改,则返回`403 Forbidden`。 1172 | 1173 | 比如,下面的请求会更改所有的`tag`: 1174 | 1175 | ```javascript 1176 | PATCH /articles/1/relationships/tags HTTP/1.1 1177 | Content-Type: application/vnd.api+json 1178 | Accept: application/vnd.api+json 1179 | 1180 | { 1181 | "data": [ 1182 | { "type": "tags", "id": "2" }, 1183 | { "type": "tags", "id": "3" } 1184 | ] 1185 | } 1186 | ``` 1187 | 1188 | 而下面的请求会清除所有的`tag`: 1189 | 1190 | ```javascript 1191 | PATCH /articles/1/relationships/tags HTTP/1.1 1192 | Content-Type: application/vnd.api+json 1193 | Accept: application/vnd.api+json 1194 | 1195 | { 1196 | "data": [] 1197 | } 1198 | ``` 1199 | 1200 | 如果客户端向一个关联连接的URL发出`POST`请求,那么服务器必须向关联添加新增的成员, 1201 | 除非他们已经存在。如果所给的`type`和`id`已经存在,服务器不能再添加他们。 1202 | 1203 | 如果所有指定的资源都可以被添加到关联,或者已经存在在关联里,那么服务器必须返回一个成功响应。 1204 | 1205 | 在下面的例子中,ID为`123`的评论被添加到评论列表内ID为`1`的条目中。 1206 | 1207 | ```javascript 1208 | POST /articles/1/relationships/comments HTTP/1.1 1209 | Content-Type: application/vnd.api+json 1210 | Accept: application/vnd.api+json 1211 | 1212 | { 1213 | "data": [ 1214 | { "type": "comments", "id": "123" } 1215 | ] 1216 | } 1217 | ``` 1218 | 1219 | 如果客户端向一个关联连接的URL发出`DELETE`请求,那么服务器必须在关联中删除指定的成员,或者返回`403 Forbidden`响应。 1220 | 如果所有指定的资源都可以从关联中删除,或者未存在在关联中,那么服务器必须返回一个成功响应。 1221 | 1222 | 关联成员是被与`POST`请求同样的方式指定的。 1223 | 1224 | 在下面的例子中,ID为`12`和`13`的评论被从评论列表内ID为`1`的条目中删除: 1225 | 1226 | ```javascript 1227 | DELETE /articles/1/relationships/comments HTTP/1.1 1228 | Content-Type: application/vnd.api+json 1229 | Accept: application/vnd.api+json 1230 | 1231 | { 1232 | "data": [ 1233 | { "type": "comments", "id": "12" }, 1234 | { "type": "comments", "id": "13" } 1235 | ] 1236 | } 1237 | ``` 1238 | ### 响应 1239 | 1240 | #### 202 Accepted 1241 | 1242 | 如果服务器接受关联更新请求,但在服务器响应时更新并未完成,那么服务器必须返回`202 Accepted`状态码。 1243 | 1244 | #### 204 No Content 1245 | 1246 | 如果更新成功,并且请求中的资源与结果相符,那么服务器必须或者返回`204 No Content`状态码。 1247 | 1248 | #### 200 OK 1249 | 1250 | 如果服务器接受更新,但用在请求指定以外的方式更改了目标关联,那么b必须返回`200 OK`响应。 1251 | 响应文档必须包括被更新关联的代表。 1252 | 1253 | 如果更新成功,客户端当前数据保持更新,并且服务器只响应最上层元数据,那么服务器必须返回`200 OK`状态码。 1254 | 这种情况下,服务器不能包括更新后的资源。 1255 | 1256 | #### 403 OK 1257 | 1258 | 服务器必须向不支持的更新关联请求发出`403 Forbidden`响应。 1259 | 1260 | #### 其它响应 1261 | 1262 | 服务器可能使用其它HTTP错误状态码反映错误。 1263 | 1264 | 服务器可能响应包括错误详情的错误响应。 1265 | 1266 | 服务器与客户端必须依照HTTP的语义准备和解译响应。 1267 | 1268 | 1269 | ### 资源删除 1270 | 1271 | 向资源URL发出`DELETE`请求即可删除单个资源。 1272 | 1273 | ```text 1274 | DELETE /photos/1 HTTP/1.1 1275 | Accept: application/vnd.api+json 1276 | ``` 1277 | 1278 | #### 响应 1279 | 1280 | ##### 202 Accepted 1281 | 1282 | 如果删除资源的请求被接受处理,但在服务器响应时处理并未完成,那么服务器必须返回`202 Accepted`状态码。 1283 | 1284 | ##### 204 No Content 1285 | 1286 | 如果删除请求成功,服务器必须返回`204 No Content` 状态码,并且没有返回内容 1287 | 1288 | ##### 200 OK 1289 | 1290 | 如果删除请求成功,服务器必须返回`200 OK`状态码,并且返回上层的元数据。 1291 | 1292 | ##### 404 NOT FOUND 1293 | 1294 | 如果因为资源不存在,删除请求失败,那么服务器应该返回`404 Not Found`状态码。 1295 | 1296 | ##### 其它响应 1297 | 1298 | 服务器可能使用其它HTTP错误状态码反映错误。 1299 | 1300 | 服务器可能响应包括错误详情的错误响应。 1301 | 1302 | 服务器与客户端必须依照HTTP的语义准备和解译响应。 1303 | 1304 | 1305 | ## 查询参数 1306 | 1307 | 查询参数必须遵守成员命名的规则,同时必须包含至少一个非a-z字符(U+0061 to U+007A)。推荐使用 U+002D HYPHEN-MINUS, “-“, U+005F LOW LINE, “_”, 或者大写字母(如: camelCasing)。 1308 | 1309 | 如果服务器获取到不遵从以上命名规则的查询参数,并且不知道如何处理,那么服务器必须返回`400 Bad Request`。 1310 | 1311 | 1312 | ## 错误 1313 | 1314 | #### 处理错误 1315 | 1316 | 当遇到一个问题时,服务器可以选择停止处理,也可以继续处理,遇到多个问题。 1317 | 比如,服务器可能处理多个属性,并在一个响应中返回多个验证问题。 1318 | 1319 | 当服务器在一个请求中遇到多个问题,在响应中应该使用HTTP最通用的错误状态码。 1320 | 比如,`400 Bad Request`可被用于多个4xx错误,`500 Internal Server Error`可被用于多个5xx错误。 1321 | 1322 | #### 错误对象 1323 | 1324 | 错误对象提供执行操作时遇到问题的额外信息。 1325 | 在JSON API文档顶层,错误对象必须被作为`errors`键对应的数组返回。 1326 | 1327 | 错误对象可能有以下成员: 1328 | 1329 | * `id` - 特定问题的唯一标识符。 1330 | * `links` - 一个包括以下成员的连接对象: 1331 | * `about` - 指向特定问题更多具体内容的连接。 1332 | * `status` - 适用于这个问题的HTTP状态码,使用字符串表示。 1333 | * `code` - 应用特定的错误码,以字符串表示。 1334 | * `title` - 简短的,可读性高的问题总结。除了国际化本地化处理之外,不同场景下,相同的问题,值是不应该变动的。 1335 | * `detail` - 针对该问题的高可读性解释。与`title`相同,这个值可以被本地化。 1336 | * `source` - 包括错误资源的引用的对象。它也可以包括以下任意成员: 1337 | * `pointer` - 一个指向请求文档中相关实体的`JSON Pointer`。 1338 | * `parameter` - 指出引起错误的查询对象的字符串。 1339 | * `meta` - 一个包括关于错误的非标准元信息的元对象。 1340 | -------------------------------------------------------------------------------- /images/blockquote.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/justjavac/json-api-zh_CN/f33dd60e301338a59829ee2a2bfdabdff7ddc6e7/images/blockquote.gif -------------------------------------------------------------------------------- /images/forkme_right_red_aa0000.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/justjavac/json-api-zh_CN/f33dd60e301338a59829ee2a2bfdabdff7ddc6e7/images/forkme_right_red_aa0000.png -------------------------------------------------------------------------------- /images/jsonapi@0.7x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/justjavac/json-api-zh_CN/f33dd60e301338a59829ee2a2bfdabdff7ddc6e7/images/jsonapi@0.7x.png -------------------------------------------------------------------------------- /images/jsonapi@1.3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/justjavac/json-api-zh_CN/f33dd60e301338a59829ee2a2bfdabdff7ddc6e7/images/jsonapi@1.3x.png -------------------------------------------------------------------------------- /index.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page 3 | title: "用 JSON 构建 API 的标准指南" 4 | show_masthead: true 5 | --- 6 | 7 | 如果你和你的团队曾经争论过使用什么方式构建合理 JSON 响应格式, 8 | 那么 JSON API 就是你的 anti-bikeshedding 武器。 9 | 10 | 通过遵循共同的约定,可以提高开发效率,利用更普遍的工具,可以是你更加专注于开发重点:你的程序。 11 | 12 | 基于 JSON API 的客户端还能够充分利用缓存,以提升性能,有时甚至可以完全不需要网络请求。 13 | 14 | 下面是一个使用 JSON API 发送响应(response)的示例: 15 | 16 | ```javascript 17 | { 18 | "links": { 19 | "posts.author": { 20 | "href": "http://example.com/people/{posts.author}", 21 | "type": "people" 22 | }, 23 | "posts.comments": { 24 | "href": "http://example.com/comments/{posts.comments}", 25 | "type": "comments" 26 | } 27 | }, 28 | "posts": [{ 29 | "id": "1", 30 | "title": "Rails is Omakase", 31 | "links": { 32 | "author": "9", 33 | "comments": [ "5", "12", "17", "20" ] 34 | } 35 | }] 36 | } 37 | ``` 38 | 39 | 顶级的 `"links"` 部分是可选的。 40 | 除去 `"links"` 部分,此响应看起来非常接近使用已经存在的 API 构建的响应。 41 | 42 | JSON API 不仅可以用来构建响应,还包括创建和更新资源。 43 | 44 | {% include status.md %} 45 | 46 | ## MIME 类型 47 | 48 | JSON API 已经在 IANA 机构完成注册。 49 | 它的 MIME 类型是 [`application/vnd.api+json`](http://www.iana.org/assignments/media-types/application/vnd.api+json)。 50 | 51 | ## 格式 52 | 53 | 在开始使用 JSON API 前,先查看一下[JSON API 格式文档](/format) 54 | 55 | ## 更新历史 56 | 57 | - 2013-05-03:最初版本的草案。 58 | - 2013-07-22:媒体类型在 IANA 注册完成。 59 | 60 | 你可以使用 RSS 阅读器在[这里](https://github.com/json-api/json-api/commits.atom)订阅本提要的变更。 61 | -------------------------------------------------------------------------------- /javascripts/all.js: -------------------------------------------------------------------------------- 1 | //= require_tree . -------------------------------------------------------------------------------- /javascripts/highlight.pack.js: -------------------------------------------------------------------------------- 1 | var hljs=new function(){function l(o){return o.replace(/&/gm,"&").replace(//gm,">")}function b(p){for(var o=p.firstChild;o;o=o.nextSibling){if(o.nodeName=="CODE"){return o}if(!(o.nodeType==3&&o.nodeValue.match(/\s+/))){break}}}function h(p,o){return Array.prototype.map.call(p.childNodes,function(q){if(q.nodeType==3){return o?q.nodeValue.replace(/\n/g,""):q.nodeValue}if(q.nodeName=="BR"){return"\n"}return h(q,o)}).join("")}function a(q){var p=(q.className+" "+q.parentNode.className).split(/\s+/);p=p.map(function(r){return r.replace(/^language-/,"")});for(var o=0;o"}while(x.length||v.length){var u=t().splice(0,1)[0];y+=l(w.substr(p,u.offset-p));p=u.offset;if(u.event=="start"){y+=s(u.node);r.push(u.node)}else{if(u.event=="stop"){var o,q=r.length;do{q--;o=r[q];y+=("")}while(o!=u.node);r.splice(q,1);while(q'+L[0]+""}else{r+=L[0]}N=A.lR.lastIndex;L=A.lR.exec(K)}return r+K.substr(N)}function z(){if(A.sL&&!e[A.sL]){return l(w)}var r=A.sL?d(A.sL,w):g(w);if(A.r>0){v+=r.keyword_count;B+=r.r}return''+r.value+""}function J(){return A.sL!==undefined?z():G()}function I(L,r){var K=L.cN?'':"";if(L.rB){x+=K;w=""}else{if(L.eB){x+=l(r)+K;w=""}else{x+=K;w=r}}A=Object.create(L,{parent:{value:A}});B+=L.r}function C(K,r){w+=K;if(r===undefined){x+=J();return 0}var L=o(r,A);if(L){x+=J();I(L,r);return L.rB?0:r.length}var M=s(A,r);if(M){if(!(M.rE||M.eE)){w+=r}x+=J();do{if(A.cN){x+=""}A=A.parent}while(A!=M.parent);if(M.eE){x+=l(r)}w="";if(M.starts){I(M.starts,"")}return M.rE?0:r.length}if(t(r,A)){throw"Illegal"}w+=r;return r.length||1}var F=e[D];f(F);var A=F;var w="";var B=0;var v=0;var x="";try{var u,q,p=0;while(true){A.t.lastIndex=p;u=A.t.exec(E);if(!u){break}q=C(E.substr(p,u.index-p),u[0]);p=u.index+q}C(E.substr(p));return{r:B,keyword_count:v,value:x,language:D}}catch(H){if(H=="Illegal"){return{r:0,keyword_count:0,value:l(E)}}else{throw H}}}function g(s){var o={keyword_count:0,r:0,value:l(s)};var q=o;for(var p in e){if(!e.hasOwnProperty(p)){continue}var r=d(p,s);r.language=p;if(r.keyword_count+r.r>q.keyword_count+q.r){q=r}if(r.keyword_count+r.r>o.keyword_count+o.r){q=o;o=r}}if(q.language){o.second_best=q}return o}function i(q,p,o){if(p){q=q.replace(/^((<[^>]+>|\t)+)/gm,function(r,v,u,t){return v.replace(/\t/g,p)})}if(o){q=q.replace(/\n/g,"
")}return q}function m(r,u,p){var v=h(r,p);var t=a(r);if(t=="no-highlight"){return}var w=t?d(t,v):g(v);t=w.language;var o=c(r);if(o.length){var q=document.createElement("pre");q.innerHTML=w.value;w.value=j(o,c(q),v)}w.value=i(w.value,u,p);var s=r.className;if(!s.match("(\\s|^)(language-)?"+t+"(\\s|$)")){s=s?(s+" "+t):t}r.innerHTML=w.value;r.className=s;r.result={language:t,kw:w.keyword_count,re:w.r};if(w.second_best){r.second_best={language:w.second_best.language,kw:w.second_best.keyword_count,re:w.second_best.r}}}function n(){if(n.called){return}n.called=true;Array.prototype.map.call(document.getElementsByTagName("pre"),b).filter(Boolean).forEach(function(o){m(o,hljs.tabReplace)})}function k(){window.addEventListener("DOMContentLoaded",n,false);window.addEventListener("load",n,false)}var e={};this.LANGUAGES=e;this.highlight=d;this.highlightAuto=g;this.fixMarkup=i;this.highlightBlock=m;this.initHighlighting=n;this.initHighlightingOnLoad=k;this.IR="[a-zA-Z][a-zA-Z0-9_]*";this.UIR="[a-zA-Z_][a-zA-Z0-9_]*";this.NR="\\b\\d+(\\.\\d+)?";this.CNR="(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)";this.BNR="\\b(0b[01]+)";this.RSR="!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|\\.|-|-=|/|/=|:|;|<|<<|<<=|<=|=|==|===|>|>=|>>|>>=|>>>|>>>=|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~";this.BE={b:"\\\\[\\s\\S]",r:0};this.ASM={cN:"string",b:"'",e:"'",i:"\\n",c:[this.BE],r:0};this.QSM={cN:"string",b:'"',e:'"',i:"\\n",c:[this.BE],r:0};this.CLCM={cN:"comment",b:"//",e:"$"};this.CBLCLM={cN:"comment",b:"/\\*",e:"\\*/"};this.HCM={cN:"comment",b:"#",e:"$"};this.NM={cN:"number",b:this.NR,r:0};this.CNM={cN:"number",b:this.CNR,r:0};this.BNM={cN:"number",b:this.BNR,r:0};this.inherit=function(q,r){var o={};for(var p in q){o[p]=q[p]}if(r){for(var p in r){o[p]=r[p]}}return o}}();hljs.LANGUAGES.ruby=function(e){var a="[a-zA-Z_][a-zA-Z0-9_]*(\\!|\\?)?";var j="[a-zA-Z_]\\w*[!?=]?|[-+~]\\@|<<|>>|=~|===?|<=>|[<>]=?|\\*\\*|[-/+%^&*~`|]|\\[\\]=?";var g={keyword:"and false then defined module in return redo if BEGIN retry end for true self when next until do begin unless END rescue nil else break undef not super class case require yield alias while ensure elsif or include"};var c={cN:"yardoctag",b:"@[A-Za-z]+"};var k=[{cN:"comment",b:"#",e:"$",c:[c]},{cN:"comment",b:"^\\=begin",e:"^\\=end",c:[c],r:10},{cN:"comment",b:"^__END__",e:"\\n$"}];var d={cN:"subst",b:"#\\{",e:"}",l:a,k:g};var i=[e.BE,d];var b=[{cN:"string",b:"'",e:"'",c:i,r:0},{cN:"string",b:'"',e:'"',c:i,r:0},{cN:"string",b:"%[qw]?\\(",e:"\\)",c:i},{cN:"string",b:"%[qw]?\\[",e:"\\]",c:i},{cN:"string",b:"%[qw]?{",e:"}",c:i},{cN:"string",b:"%[qw]?<",e:">",c:i,r:10},{cN:"string",b:"%[qw]?/",e:"/",c:i,r:10},{cN:"string",b:"%[qw]?%",e:"%",c:i,r:10},{cN:"string",b:"%[qw]?-",e:"-",c:i,r:10},{cN:"string",b:"%[qw]?\\|",e:"\\|",c:i,r:10}];var h={cN:"function",bWK:true,e:" |$|;",k:"def",c:[{cN:"title",b:j,l:a,k:g},{cN:"params",b:"\\(",e:"\\)",l:a,k:g}].concat(k)};var f=k.concat(b.concat([{cN:"class",bWK:true,e:"$|;",k:"class module",c:[{cN:"title",b:"[A-Za-z_]\\w*(::\\w+)*(\\?|\\!)?",r:0},{cN:"inheritance",b:"<\\s*",c:[{cN:"parent",b:"("+e.IR+"::)?"+e.IR}]}].concat(k)},h,{cN:"constant",b:"(::)?(\\b[A-Z]\\w*(::)?)+",r:0},{cN:"symbol",b:":",c:b.concat([{b:j}]),r:0},{cN:"symbol",b:a+":",r:0},{cN:"number",b:"(\\b0[0-7_]+)|(\\b0x[0-9a-fA-F_]+)|(\\b[1-9][0-9_]*(\\.[0-9_]+)?)|[0_]\\b",r:0},{cN:"number",b:"\\?\\w"},{cN:"variable",b:"(\\$\\W)|((\\$|\\@\\@?)(\\w+))"},{b:"("+e.RSR+")\\s*",c:k.concat([{cN:"regexp",b:"/",e:"/[a-z]*",i:"\\n",c:[e.BE,d]}]),r:0}]));d.c=f;h.c[1].c=f;return{l:a,k:g,c:f}}(hljs);hljs.LANGUAGES.javascript=function(a){return{k:{keyword:"in if for while finally var new function do return void else break catch instanceof with throw case default try this switch continue typeof delete let yield const",literal:"true false null undefined NaN Infinity"},c:[a.ASM,a.QSM,a.CLCM,a.CBLCLM,a.CNM,{b:"("+a.RSR+"|\\b(case|return|throw)\\b)\\s*",k:"return throw case",c:[a.CLCM,a.CBLCLM,{cN:"regexp",b:"/",e:"/[gim]*",i:"\\n",c:[{b:"\\\\/"}]},{b:"<",e:">;",sL:"xml"}],r:0},{cN:"function",bWK:true,e:"{",k:"function",c:[{cN:"title",b:"[A-Za-z$_][0-9A-Za-z$_]*"},{cN:"params",b:"\\(",e:"\\)",c:[a.CLCM,a.CBLCLM],i:"[\"'\\(]"}],i:"\\[|%"}]}}(hljs);hljs.LANGUAGES.css=function(a){var b={cN:"function",b:a.IR+"\\(",e:"\\)",c:[a.NM,a.ASM,a.QSM]};return{cI:true,i:"[=/|']",c:[a.CBLCLM,{cN:"id",b:"\\#[A-Za-z0-9_-]+"},{cN:"class",b:"\\.[A-Za-z0-9_-]+",r:0},{cN:"attr_selector",b:"\\[",e:"\\]",i:"$"},{cN:"pseudo",b:":(:)?[a-zA-Z0-9\\_\\-\\+\\(\\)\\\"\\']+"},{cN:"at_rule",b:"@(font-face|page)",l:"[a-z-]+",k:"font-face page"},{cN:"at_rule",b:"@",e:"[{;]",eE:true,k:"import page media charset",c:[b,a.ASM,a.QSM,a.NM]},{cN:"tag",b:a.IR,r:0},{cN:"rules",b:"{",e:"}",i:"[^\\s]",r:0,c:[a.CBLCLM,{cN:"rule",b:"[^\\s]",rB:true,e:";",eW:true,c:[{cN:"attribute",b:"[A-Z\\_\\.\\-]+",e:":",eE:true,i:"[^\\s]",starts:{cN:"value",eW:true,eE:true,c:[b,a.NM,a.QSM,a.ASM,a.CBLCLM,{cN:"hexcolor",b:"\\#[0-9A-F]+"},{cN:"important",b:"!important"}]}}]}]}]}}(hljs);hljs.LANGUAGES.xml=function(a){var c="[A-Za-z0-9\\._:-]+";var b={eW:true,c:[{cN:"attribute",b:c,r:0},{b:'="',rB:true,e:'"',c:[{cN:"value",b:'"',eW:true}]},{b:"='",rB:true,e:"'",c:[{cN:"value",b:"'",eW:true}]},{b:"=",c:[{cN:"value",b:"[^\\s/>]+"}]}]};return{cI:true,c:[{cN:"pi",b:"<\\?",e:"\\?>",r:10},{cN:"doctype",b:"",r:10,c:[{b:"\\[",e:"\\]"}]},{cN:"comment",b:"",r:10},{cN:"cdata",b:"<\\!\\[CDATA\\[",e:"\\]\\]>",r:10},{cN:"tag",b:"|$)",e:">",k:{title:"style"},c:[b],starts:{e:"",rE:true,sL:"css"}},{cN:"tag",b:"|$)",e:">",k:{title:"script"},c:[b],starts:{e:"<\/script>",rE:true,sL:"javascript"}},{b:"<%",e:"%>",sL:"vbscript"},{cN:"tag",b:"",c:[{cN:"title",b:"[^ />]+"},b]}]}}(hljs);hljs.LANGUAGES.http=function(a){return{i:"\\S",c:[{cN:"status",b:"^HTTP/[0-9\\.]+",e:"$",c:[{cN:"number",b:"\\b\\d{3}\\b"}]},{cN:"request",b:"^[A-Z]+ (.*?) HTTP/[0-9\\.]+$",rB:true,e:"$",c:[{cN:"string",b:" ",e:" ",eB:true,eE:true}]},{cN:"attribute",b:"^\\w",e:": ",eE:true,i:"\\n|\\s|=",starts:{cN:"string",e:"$"}},{b:"\\n\\n",starts:{sL:"",eW:true}}]}}(hljs);hljs.LANGUAGES.php=function(a){var e={cN:"variable",b:"\\$+[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*"};var b=[a.inherit(a.ASM,{i:null}),a.inherit(a.QSM,{i:null}),{cN:"string",b:'b"',e:'"',c:[a.BE]},{cN:"string",b:"b'",e:"'",c:[a.BE]}];var c=[a.BNM,a.CNM];var d={cN:"title",b:a.UIR};return{cI:true,k:"and include_once list abstract global private echo interface as static endswitch array null if endwhile or const for endforeach self var while isset public protected exit foreach throw elseif include __FILE__ empty require_once do xor return implements parent clone use __CLASS__ __LINE__ else break print eval new catch __METHOD__ case exception php_user_filter default die require __FUNCTION__ enddeclare final try this switch continue endfor endif declare unset true false namespace trait goto instanceof insteadof __DIR__ __NAMESPACE__ __halt_compiler",c:[a.CLCM,a.HCM,{cN:"comment",b:"/\\*",e:"\\*/",c:[{cN:"phpdoc",b:"\\s@[A-Za-z]+"}]},{cN:"comment",eB:true,b:"__halt_compiler.+?;",eW:true},{cN:"string",b:"<<<['\"]?\\w+['\"]?$",e:"^\\w+;",c:[a.BE]},{cN:"preprocessor",b:"<\\?php",r:10},{cN:"preprocessor",b:"\\?>"},e,{cN:"function",bWK:true,e:"{",k:"function",i:"\\$|\\[|%",c:[d,{cN:"params",b:"\\(",e:"\\)",c:["self",e,a.CBLCLM].concat(b).concat(c)}]},{cN:"class",bWK:true,e:"{",k:"class",i:"[:\\(\\$]",c:[{bWK:true,eW:true,k:"extends",c:[d]},d]},{b:"=>"}].concat(b).concat(c)}}(hljs);hljs.LANGUAGES.python=function(a){var f={cN:"prompt",b:"^(>>>|\\.\\.\\.) "};var c=[{cN:"string",b:"(u|b)?r?'''",e:"'''",c:[f],r:10},{cN:"string",b:'(u|b)?r?"""',e:'"""',c:[f],r:10},{cN:"string",b:"(u|r|ur)'",e:"'",c:[a.BE],r:10},{cN:"string",b:'(u|r|ur)"',e:'"',c:[a.BE],r:10},{cN:"string",b:"(b|br)'",e:"'",c:[a.BE]},{cN:"string",b:'(b|br)"',e:'"',c:[a.BE]}].concat([a.ASM,a.QSM]);var e={cN:"title",b:a.UIR};var d={cN:"params",b:"\\(",e:"\\)",c:["self",a.CNM,f].concat(c)};var b={bWK:true,e:":",i:"[${=;\\n]",c:[e,d],r:10};return{k:{keyword:"and elif is global as in if from raise for except finally print import pass return exec else break not with class assert yield try while continue del or def lambda nonlocal|10",built_in:"None True False Ellipsis NotImplemented"},i:"(|\\?)",c:c.concat([f,a.HCM,a.inherit(b,{cN:"function",k:"def"}),a.inherit(b,{cN:"class",k:"class"}),a.CNM,{cN:"decorator",b:"@",e:"$"},{b:"\\b(print|exec)\\("}])}}(hljs);hljs.LANGUAGES.json=function(a){var e={literal:"true false null"};var d=[a.QSM,a.CNM];var c={cN:"value",e:",",eW:true,eE:true,c:d,k:e};var b={b:"{",e:"}",c:[{cN:"attribute",b:'\\s*"',e:'"\\s*:\\s*',eB:true,eE:true,c:[a.BE],i:"\\n",starts:c}],i:"\\S"};var f={b:"\\[",e:"\\]",c:[a.inherit(c,{cN:null})],i:"\\S"};d.splice(d.length,0,b,f);return{c:d,k:e,i:"\\S"}}(hljs); -------------------------------------------------------------------------------- /recommendation/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page 3 | title: "推荐规范" 4 | --- 5 | 6 | 7 | 本节介绍实现JSON API的推荐规范。这些推荐规范用于对基本JSON API规范的补充,以保证基本规范以外部分的一致性。 8 | 9 | ##命名 10 | 11 | 用于键的URL安全命名的合法字符和推荐字符已经在格式说明部分定义。同样为了规范键命名方式,推荐以下(更严格的)规则: 12 | 13 | * 键名称应该以字符"a-z"(U+0061 to U+007A)开始和结束. 14 | * 键名称应该只包含字符“a-z” (U+0061 to U+007A)和 “0-9” (U+0030 to U+0039),并且以减号连字符(U+002D HYPHEN-MINUS, “-“) 作为单词之间的分隔符. 15 | 16 | ##URL设计 17 | 18 | ###相关文档 19 | 20 | 在一个"reference document"中,每个资源都可通过一个特有路径寻址。考虑"reference document"中的所有资源对于设计一个API的URL结构很有帮助。资源在这个文档的top-level中以类型分组。每个资源在这些类型集合中以ID标识。每个资源中的属性和链接可以通过前文所述的资源对象结构寻址。 21 | 22 | 相关文档的概念可用于为资源和它们的关联设计URL。由于目标和规则不同,此处的相关文档与用于传输文档有少许不同之处。例如,群组在相关文档中以集合形式表现,因为群组的成员必须通过ID寻址;但是在传输文档中以数组形式表现,因为它是有序的。 23 | 24 | ###资源组URL 25 | 26 | 推荐利用资源类型构成资源组的URL。 27 | 28 | 例如,一组`photos`类型的资源可以通过以下URL定义: 29 | 30 | ```javascript 31 | /photos 32 | ``` 33 | 34 | ###资源个体URL 35 | 36 | 将资源组看做以资源ID标识的集合。每个资源个体的URL可以通过在资源组URL后附加资源ID构成。 37 | 38 | 例如,ID为`"1"`的照片可以通过以下URL定义: 39 | 40 | ```javascript 41 | /photos/1 42 | ``` 43 | 44 | ###关联URL和相关资源URL 45 | 46 | 如基本规范所述,对于每个关联,可以有两个URL: 47 | 48 | * "relationship URL": 表述关联本身的URL,用关联的`links`对象中的`self`键定义。客户端可以通过这个URL直接操作关联。例如,客户端可以通过这个URL利用`POST`请求移除`author`,而不需要删除`people`资源本省。 49 | 50 | * “related resource URL” : 指向相关资源的URL,用关联的`links`对象中的`related`键定义。当通过这个URL获取数据,将会回复将相关资源对象作为主数据的响应。 51 | 52 | 推荐通过在资源URL后附加`/relationships/`和关联名称构成关联URL。 53 | 54 | 例如,一张照片的`comments`关联可以通过以下URL定义: 55 | 56 | ```javascript 57 | /photos/1/relationships/comments 58 | ``` 59 | 60 | 一张照片的`photographer`关联可以通过以下URL定义: 61 | 62 | ```javascript 63 | /photos/1/relationships/photographer 64 | ``` 65 | 66 | 推荐通过在资源URL后附加关联名称构成相关资源URL。 67 | 68 | 例如,一张照片的`comments`可以通过以下URL定义: 69 | 70 | ```javascript 71 | /photos/1/comments 72 | ``` 73 | 74 | 一张照片的`photographer`可以通过以下URL定义: 75 | 76 | ```javascript 77 | /photos/1/photographer 78 | ``` 79 | 80 | 由于这些URL表示关联中的资源,因此他们不能用于资源本省的`self`链接。当构成`self`链接时,资源个体URL推荐规范仍然使用。 81 | 82 | 83 | 84 | ## Filtering 85 | 86 | 基础规范并未规定服务器支持的过滤规则。查询参数`filter`是用于任何查询策略的保留字。 87 | 88 | 如果服务器希望支持基于associations的资源集合的过滤,推荐允许查询参数将`filter`与association名称相结合。 89 | 90 | 比如,下面是一个对与一个post相关的所有评论的请求: 91 | 92 | ```javascript 93 | GET /comments?filter[post]=1 HTTP/1.1 94 | ``` 95 | 多个过滤值可以用逗号分隔开。比如: 96 | 97 | ```javascript 98 | GET /comments?filter[post]=1,2 HTTP/1.1 99 | ``` 100 | 101 | 一个请求可以包括多个过滤: 102 | 103 | ```javascript 104 | GET /comments?filter[post]=1,2&filter[author]=12 HTTP/1.1 105 | ``` 106 | 107 | ## Top-level, 资源层链接以及关联链接 108 | 109 | 基础规范并未规定包括带有资源响应的连接。然而,推荐将以下链接包括在响应文档里。 110 | 111 | 112 | * **Top-level链接** 整个响应的自链接以及相关的分页链接. 113 | * **资源层链接** 每个资源的自链接(如果资源是集合的一部分,此链接不同于top-level链接). 114 | * **关联链接** 用于表述资源中的所有可用关联. 115 | 116 | 比如,一个对评论集合的请求可以引起如下响应: 117 | 118 | ```javascript 119 | GET /comments HTTP/1.1 120 | 121 | { 122 | "data": [{ 123 | "type": "comments", 124 | "id": "1", 125 | "attributes": { 126 | "text": "HATEOS are the thing!" 127 | }, 128 | "links": { 129 | "self": "/comments/1" 130 | }, 131 | "relationships": { 132 | "author": { 133 | "links": { 134 | "self": "/comments/1/relationships/author", 135 | "related": "/comments/1/author" 136 | } 137 | }, 138 | "articles": { 139 | "links": { 140 | "self": "/comments/1/relationships/articles", 141 | "related": "/comments/1/articles" 142 | } 143 | } 144 | } 145 | }], 146 | "links": { 147 | "self": "/comments" 148 | } 149 | } 150 | ``` 151 | 152 | ## 支持缺少PATCH的客户端 153 | 154 | 一些客户端,如IE8,并不支持HTTP`PATCH`方法。如果API服务器希望支持这些客户端的, 155 | 建议当客户端报头包括`X-HTTP-Method-Override: PATCH`时,把`POST`请求当做`PATCH`请求。 156 | 这样,只需通过添加报头,缺少`PATCH`支持的客户端就可以拥有自己的更新请求。 157 | 158 | ## 日期和时间字段的格式 159 | 160 | 虽然JSON API并未指定日期和时间字段的格式,但推荐服务器使用与ISO 8601相符的格式。 161 | 这个页面[[W3C NOTE](https://www.w3.org/TR/NOTE-datetime)]提供了推荐格式的概述。 162 | 163 | ## 异步处理 164 | 165 | 考虑当你需要创建一个资源时操作需要很长时间的情况。 166 | 167 | ```javascript 168 | POST /photos HTTP/1.1 169 | ``` 170 | 171 | 这个请求应该返回一个`202 Accepted`的状态码,其报头中`Content-Location`有一个链接。 172 | 173 | ```javascript 174 | HTTP/1.1 202 Accepted 175 | Content-Type: application/vnd.api+json 176 | Content-Location: https://example.com/photos/queue-jobs/5234 177 | 178 | { 179 | "data": { 180 | "type": "queue-jobs", 181 | "id": "5234", 182 | "attributes": { 183 | "status": "Pending request, waiting other process" 184 | }, 185 | "links": { 186 | "self": "/photos/queue-jobs/5234" 187 | } 188 | } 189 | } 190 | ``` 191 | 192 | 如需检查处理的状态,客户端可以向所给地址更早发出请求。 193 | 194 | ```javascript 195 | GET /photos/queue-jobs/5234 HTTP/1.1 196 | Accept: application/vnd.api+json 197 | ``` 198 | 199 | 当处理完成时,请求应该返回一个`303 See other`的状态码,其报头中`Location`有一个链接。 200 | 201 | ```javascript 202 | HTTP/1.1 303 See other 203 | Content-Type: application/vnd.api+json 204 | Location: https://example.com/photos/4577 205 | ``` -------------------------------------------------------------------------------- /schema: -------------------------------------------------------------------------------- 1 | { 2 | "id": "http://jsonapi.org/schema#", 3 | "$schema": "http://json-schema.org/draft-04/schema#", 4 | "title": "JSON API Schema", 5 | "description": "This is a schema for responses in the JSON API format. For more, see http://jsonapi.org", 6 | "type": "object", 7 | "resources":{ 8 | "type": "array", 9 | "items": { 10 | "type": "object", 11 | "properties": { 12 | "id": { "type":["string"] }, 13 | "href": { "type":"string" }, 14 | "links": { "type": "object" } 15 | }, 16 | "required": ["id"] 17 | } 18 | }, 19 | "patternProperties": { 20 | "^(?!href$)(?!links$)(?!id$)(?!meta)(?!linked)": { 21 | "$ref":"#/resources" 22 | } 23 | }, 24 | "properties": { 25 | "meta": { 26 | "type": "object" 27 | }, 28 | "links":{ 29 | "type": "object" 30 | }, 31 | "linked": { 32 | "type": "object", 33 | "patternProperties": { 34 | ".*": { 35 | "$ref":"#/resources" 36 | } 37 | } 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /status/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page 3 | title: "JSON API:规范的现状" 4 | --- 5 | 6 | **本文档是一个正在进展的工作**,在具体实现过程中将会有所改变。 7 | 实现者应该意识到这个规范是不稳定的。 8 | 目前缺少一些关于 `meta` 属性的细节和更精确的关于**关联**的细节。 9 | 10 | 此规范是在 [GitHub repository](https://github.com/justjavac/json-api) 上完成。 11 | 因此你可以帮助我们更新此规范,或者如果你想写一个关于此规范的具体实现,告诉我们哪些地方描述的可能存在歧义。 12 | 最好的方式就是提交 [Issue](https://github.com/justjavac/json-api/issues)。 13 | -------------------------------------------------------------------------------- /stylesheets/all.css: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | 3 | body { 4 | margin: 52px 0 0 0; 5 | line-height: 150%; 6 | font-family: Helvetica, arial, freesans, clean, sans-serif, "Segoe UI Emoji", "Segoe UI Symbol"; 7 | color: #222; 8 | background: #eee; } 9 | 10 | a { 11 | color: #393; } 12 | a:hover { 13 | color: #050; } 14 | 15 | h1 { 16 | margin: 1.5em 0 1.2em 0; 17 | padding: 0; 18 | font-size: 1.8em; 19 | font-weight: bold; 20 | text-align: center; } 21 | 22 | .masthead { 23 | margin: 20px 0; 24 | padding: 30px; 25 | text-align: center; 26 | background: #f9f9f9; 27 | -webkit-border-radius: 8px; 28 | -moz-border-radius: 8px; 29 | -ms-border-radius: 8px; 30 | -o-border-radius: 8px; 31 | border-radius: 8px; 32 | border: 1px solid #eee; } 33 | .masthead h1 { 34 | margin: 0.5em; } 35 | 36 | .headerlink { 37 | text-decoration: none; } 38 | 39 | h2:hover .headerlink:after, 40 | h3:hover .headerlink:after, 41 | h4:hover .headerlink:after, 42 | h5:hover .headerlink:after { 43 | content: "¶"; } 44 | 45 | h2 { 46 | margin: 1.5em 0 0.9em 0; 47 | font-size: 1.6em; 48 | line-height: 1.8em; 49 | border-bottom: 1px solid #eee; } 50 | 51 | h3 { 52 | margin: 1.5em 0 1em 0; 53 | font-size: 1.4em;} 54 | 55 | h4 { 56 | margin: 1.2em 0 1em 0; 57 | font-size: 1.2em;} 58 | 59 | h5 { 60 | margin: 1.2em 0 1em 0; 61 | font-size: 1.1em;} 62 | 63 | ul { 64 | margin-left: 0; 65 | padding-left: 0; } 66 | ul li { 67 | margin-left: 20px; } 68 | 69 | section { 70 | margin: 0 auto; 71 | width: 800px; } 72 | section:after { 73 | clear: both; } 74 | 75 | header { 76 | position: absolute; 77 | width: 100%; 78 | min-width: 800px; 79 | top: 0; 80 | left: 0; 81 | height: 52px; 82 | border-bottom: 1px solid #aaa; 83 | z-index: 100; } 84 | header a.logo { 85 | margin: 1px 10px 0 0; 86 | float: left; } 87 | header nav { 88 | float: left; 89 | margin-top: 13px; } 90 | header nav ul { 91 | margin: 0; 92 | padding-left: 0; } 93 | header nav li { 94 | display: inline; 95 | font-weight: bold; 96 | list-style-type: none; 97 | margin: 0 0 0.5em 0; 98 | padding: 0; 99 | text-align: right; 100 | white-space: pre; } 101 | header nav li a { 102 | font-family: monospace, serif; 103 | text-decoration: none; 104 | padding: 0.3em 0.5em; 105 | color: #777; } 106 | header nav li.active a { 107 | color: #000; } 108 | 109 | article { 110 | position: relative; 111 | margin: 0 auto; 112 | padding: 0 0 40px 0; 113 | overflow: hidden; 114 | background: #fff; } 115 | 116 | footer { 117 | padding: 15px 0; 118 | border-top: 1px solid #aaa; } 119 | footer .details { 120 | float: left; } 121 | footer .links { 122 | float: right; } 123 | 124 | .links { 125 | width: 200px; 126 | text-align: right; } 127 | .links a { 128 | margin: 3px 10px 0; 129 | border-bottom: none; 130 | opacity: 0.7; 131 | /* 132 | This is for hiding text from users with normal vision 133 | but keeping the text for screen readers 134 | */ } 135 | .links a:hover { 136 | opacity: 1; } 137 | .links a i { 138 | margin: 0; } 139 | 140 | .github, .weibo { 141 | font-size: 33px; 142 | line-height: 35px; 143 | color: #464646; 144 | height: 35px; 145 | width: 35px; 146 | display: inline-block; } 147 | 148 | blockquote { 149 | margin: 18px 0 18px; 150 | border: 1px dashed #CCC; 151 | padding: 0 12px 8px 36px; 152 | background: #F4F5F7 url(../images/blockquote.gif) 3px 3px no-repeat; 153 | } 154 | -------------------------------------------------------------------------------- /stylesheets/highlight.css: -------------------------------------------------------------------------------- 1 | .highlight { 2 | background: #f9f9f9; 3 | border: 1px solid #d1d1d1; 4 | -webkit-border-radius: 5px; 5 | -moz-border-radius: 5px; 6 | -ms-border-radius: 5px; 7 | -o-border-radius: 5px; 8 | border-radius: 5px; 9 | font-size: 12px; 10 | font-family: Menlo, monospace; 11 | line-height: 16px; 12 | overflow: hidden; 13 | position: relative; 14 | margin: 2em 0; 15 | -webkit-box-shadow: 0 1px white, inset -1px 1px 4px rgba(0, 0, 0, 0.1); 16 | -moz-box-shadow: 0 1px white, inset -1px 1px 4px rgba(0, 0, 0, 0.1); 17 | box-shadow: 0 1px white, inset -1px 1px 4px rgba(0, 0, 0, 0.1); } 18 | .highlight .scroller { 19 | overflow: auto; } 20 | .highlight table { 21 | margin: 0 0; } 22 | .highlight .ribbon { 23 | position: absolute; 24 | right: 0; 25 | bottom: 6px; 26 | width: 52px; 27 | height: 20px; } 28 | .highlight.javascript .ribbon, .highlight.js .ribbon { 29 | background-image: url("/images/js-ribbon.png"); } 30 | .highlight.html .ribbon { 31 | background-image: url("/images/html-ribbon.png"); } 32 | .highlight.handlebars .ribbon { 33 | background-image: url("/images/handlebars-ribbon.png"); } 34 | 35 | .highlight pre { 36 | padding-left: 1em; } 37 | 38 | /* 39 | 40 | Syntax highlighting styles for highlight.js. 41 | 42 | github.com style (c) Vasily Polovnyov 43 | 44 | */ 45 | pre code { 46 | display: block; 47 | padding: 0.5em; 48 | color: #333; } 49 | 50 | pre .comment, 51 | pre .template_comment, 52 | pre .diff .header, 53 | pre .javadoc { 54 | color: #998; 55 | font-style: italic; } 56 | 57 | pre .keyword, 58 | pre .css .rule .keyword, 59 | pre .winutils, 60 | pre .javascript .title, 61 | pre .nginx .title, 62 | pre .subst, 63 | pre .request, 64 | pre .status { 65 | color: #333; 66 | font-weight: bold; } 67 | 68 | pre .number, 69 | pre .hexcolor, 70 | pre .ruby .constant { 71 | color: #099; } 72 | 73 | pre .string, 74 | pre .tag .value, 75 | pre .phpdoc, 76 | pre .tex .formula { 77 | color: #dd1144; } 78 | 79 | pre .title, 80 | pre .id { 81 | color: #900; 82 | font-weight: bold; } 83 | 84 | pre .javascript .title, 85 | pre .lisp .title, 86 | pre .clojure .title, 87 | pre .subst { 88 | font-weight: normal; } 89 | 90 | pre .class .title, 91 | pre .haskell .type, 92 | pre .vhdl .literal, 93 | pre .tex .command { 94 | color: #458; 95 | font-weight: bold; } 96 | 97 | pre .tag, 98 | pre .tag .title, 99 | pre .rules .property, 100 | pre .django .tag .keyword { 101 | color: #000080; 102 | font-weight: normal; } 103 | 104 | pre .attribute, 105 | pre .variable, 106 | pre .lisp .body { 107 | color: teal; } 108 | 109 | pre .regexp { 110 | color: #009926; } 111 | 112 | pre .class { 113 | color: #458; 114 | font-weight: bold; } 115 | 116 | pre .symbol, 117 | pre .ruby .symbol .string, 118 | pre .lisp .keyword, 119 | pre .tex .special, 120 | pre .prompt { 121 | color: #990073; } 122 | 123 | pre .built_in, 124 | pre .lisp .title, 125 | pre .clojure .built_in { 126 | color: #0086b3; } 127 | 128 | pre .preprocessor, 129 | pre .pi, 130 | pre .doctype, 131 | pre .shebang, 132 | pre .cdata { 133 | color: #999; 134 | font-weight: bold; } 135 | 136 | pre .deletion { 137 | background: #ffdddd; } 138 | 139 | pre .addition { 140 | background: #ddffdd; } 141 | 142 | pre .diff .change { 143 | background: #0086b3; } 144 | 145 | pre .chunk { 146 | color: #aaaaaa; } 147 | -------------------------------------------------------------------------------- /stylesheets/normalize.css: -------------------------------------------------------------------------------- 1 | /*! normalize.css v2.0.1 | MIT License | git.io/normalize */ 2 | 3 | /* ========================================================================== 4 | HTML5 display definitions 5 | ========================================================================== */ 6 | 7 | /* 8 | * Corrects `block` display not defined in IE 8/9. 9 | */ 10 | 11 | article, 12 | aside, 13 | details, 14 | figcaption, 15 | figure, 16 | footer, 17 | header, 18 | hgroup, 19 | nav, 20 | section, 21 | summary { 22 | display: block; 23 | } 24 | 25 | /* 26 | * Corrects `inline-block` display not defined in IE 8/9. 27 | */ 28 | 29 | audio, 30 | canvas, 31 | video { 32 | display: inline-block; 33 | } 34 | 35 | /* 36 | * Prevents modern browsers from displaying `audio` without controls. 37 | * Remove excess height in iOS 5 devices. 38 | */ 39 | 40 | audio:not([controls]) { 41 | display: none; 42 | height: 0; 43 | } 44 | 45 | /* 46 | * Addresses styling for `hidden` attribute not present in IE 8/9. 47 | */ 48 | 49 | [hidden] { 50 | display: none; 51 | } 52 | 53 | /* ========================================================================== 54 | Base 55 | ========================================================================== */ 56 | 57 | /* 58 | * 1. Sets default font family to sans-serif. 59 | * 2. Prevents iOS text size adjust after orientation change, without disabling 60 | * user zoom. 61 | */ 62 | 63 | html { 64 | font-family: sans-serif; /* 1 */ 65 | -webkit-text-size-adjust: 100%; /* 2 */ 66 | -ms-text-size-adjust: 100%; /* 2 */ 67 | } 68 | 69 | /* 70 | * Removes default margin. 71 | */ 72 | 73 | body { 74 | margin: 0; 75 | } 76 | 77 | /* ========================================================================== 78 | Links 79 | ========================================================================== */ 80 | 81 | /* 82 | * Addresses `outline` inconsistency between Chrome and other browsers. 83 | */ 84 | 85 | a:focus { 86 | outline: thin dotted; 87 | } 88 | 89 | /* 90 | * Improves readability when focused and also mouse hovered in all browsers. 91 | */ 92 | 93 | a:active, 94 | a:hover { 95 | outline: 0; 96 | } 97 | 98 | /* ========================================================================== 99 | Typography 100 | ========================================================================== */ 101 | 102 | /* 103 | * Addresses `h1` font sizes within `section` and `article` in Firefox 4+, 104 | * Safari 5, and Chrome. 105 | */ 106 | 107 | h1 { 108 | font-size: 2em; 109 | } 110 | 111 | /* 112 | * Addresses styling not present in IE 8/9, Safari 5, and Chrome. 113 | */ 114 | 115 | abbr[title] { 116 | border-bottom: 1px dotted; 117 | } 118 | 119 | /* 120 | * Addresses style set to `bolder` in Firefox 4+, Safari 5, and Chrome. 121 | */ 122 | 123 | b, 124 | strong { 125 | font-weight: bold; 126 | } 127 | 128 | /* 129 | * Addresses styling not present in Safari 5 and Chrome. 130 | */ 131 | 132 | dfn { 133 | font-style: italic; 134 | } 135 | 136 | /* 137 | * Addresses styling not present in IE 8/9. 138 | */ 139 | 140 | mark { 141 | background: #ff0; 142 | color: #000; 143 | } 144 | 145 | 146 | /* 147 | * Corrects font family set oddly in Safari 5 and Chrome. 148 | */ 149 | 150 | code, 151 | kbd, 152 | pre, 153 | samp { 154 | font-family: monospace, serif; 155 | font-size: 1em; 156 | } 157 | 158 | /* 159 | * Improves readability of pre-formatted text in all browsers. 160 | */ 161 | 162 | pre { 163 | white-space: pre; 164 | white-space: pre-wrap; 165 | word-wrap: break-word; 166 | } 167 | 168 | /* 169 | * Sets consistent quote types. 170 | */ 171 | 172 | q { 173 | quotes: "\201C" "\201D" "\2018" "\2019"; 174 | } 175 | 176 | /* 177 | * Addresses inconsistent and variable font size in all browsers. 178 | */ 179 | 180 | small { 181 | font-size: 80%; 182 | } 183 | 184 | /* 185 | * Prevents `sub` and `sup` affecting `line-height` in all browsers. 186 | */ 187 | 188 | sub, 189 | sup { 190 | font-size: 75%; 191 | line-height: 0; 192 | position: relative; 193 | vertical-align: baseline; 194 | } 195 | 196 | sup { 197 | top: -0.5em; 198 | } 199 | 200 | sub { 201 | bottom: -0.25em; 202 | } 203 | 204 | /* ========================================================================== 205 | Embedded content 206 | ========================================================================== */ 207 | 208 | /* 209 | * Removes border when inside `a` element in IE 8/9. 210 | */ 211 | 212 | img { 213 | border: 0; 214 | } 215 | 216 | /* 217 | * Corrects overflow displayed oddly in IE 9. 218 | */ 219 | 220 | svg:not(:root) { 221 | overflow: hidden; 222 | } 223 | 224 | /* ========================================================================== 225 | Figures 226 | ========================================================================== */ 227 | 228 | /* 229 | * Addresses margin not present in IE 8/9 and Safari 5. 230 | */ 231 | 232 | figure { 233 | margin: 0; 234 | } 235 | 236 | /* ========================================================================== 237 | Forms 238 | ========================================================================== */ 239 | 240 | /* 241 | * Define consistent border, margin, and padding. 242 | */ 243 | 244 | fieldset { 245 | border: 1px solid #c0c0c0; 246 | margin: 0 2px; 247 | padding: 0.35em 0.625em 0.75em; 248 | } 249 | 250 | /* 251 | * 1. Corrects color not being inherited in IE 8/9. 252 | * 2. Remove padding so people aren't caught out if they zero out fieldsets. 253 | */ 254 | 255 | legend { 256 | border: 0; /* 1 */ 257 | padding: 0; /* 2 */ 258 | } 259 | 260 | /* 261 | * 1. Corrects font family not being inherited in all browsers. 262 | * 2. Corrects font size not being inherited in all browsers. 263 | * 3. Addresses margins set differently in Firefox 4+, Safari 5, and Chrome 264 | */ 265 | 266 | button, 267 | input, 268 | select, 269 | textarea { 270 | font-family: inherit; /* 1 */ 271 | font-size: 100%; /* 2 */ 272 | margin: 0; /* 3 */ 273 | } 274 | 275 | /* 276 | * Addresses Firefox 4+ setting `line-height` on `input` using `!important` in 277 | * the UA stylesheet. 278 | */ 279 | 280 | button, 281 | input { 282 | line-height: normal; 283 | } 284 | 285 | /* 286 | * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` 287 | * and `video` controls. 288 | * 2. Corrects inability to style clickable `input` types in iOS. 289 | * 3. Improves usability and consistency of cursor style between image-type 290 | * `input` and others. 291 | */ 292 | 293 | button, 294 | html input[type="button"], /* 1 */ 295 | input[type="reset"], 296 | input[type="submit"] { 297 | -webkit-appearance: button; /* 2 */ 298 | cursor: pointer; /* 3 */ 299 | } 300 | 301 | /* 302 | * Re-set default cursor for disabled elements. 303 | */ 304 | 305 | button[disabled], 306 | input[disabled] { 307 | cursor: default; 308 | } 309 | 310 | /* 311 | * 1. Addresses box sizing set to `content-box` in IE 8/9. 312 | * 2. Removes excess padding in IE 8/9. 313 | */ 314 | 315 | input[type="checkbox"], 316 | input[type="radio"] { 317 | box-sizing: border-box; /* 1 */ 318 | padding: 0; /* 2 */ 319 | } 320 | 321 | /* 322 | * 1. Addresses `appearance` set to `searchfield` in Safari 5 and Chrome. 323 | * 2. Addresses `box-sizing` set to `border-box` in Safari 5 and Chrome 324 | * (include `-moz` to future-proof). 325 | */ 326 | 327 | input[type="search"] { 328 | -webkit-appearance: textfield; /* 1 */ 329 | -moz-box-sizing: content-box; 330 | -webkit-box-sizing: content-box; /* 2 */ 331 | box-sizing: content-box; 332 | } 333 | 334 | /* 335 | * Removes inner padding and search cancel button in Safari 5 and Chrome 336 | * on OS X. 337 | */ 338 | 339 | input[type="search"]::-webkit-search-cancel-button, 340 | input[type="search"]::-webkit-search-decoration { 341 | -webkit-appearance: none; 342 | } 343 | 344 | /* 345 | * Removes inner padding and border in Firefox 4+. 346 | */ 347 | 348 | button::-moz-focus-inner, 349 | input::-moz-focus-inner { 350 | border: 0; 351 | padding: 0; 352 | } 353 | 354 | /* 355 | * 1. Removes default vertical scrollbar in IE 8/9. 356 | * 2. Improves readability and alignment in all browsers. 357 | */ 358 | 359 | textarea { 360 | overflow: auto; /* 1 */ 361 | vertical-align: top; /* 2 */ 362 | } 363 | 364 | /* ========================================================================== 365 | Tables 366 | ========================================================================== */ 367 | 368 | /* 369 | * Remove most spacing between table cells. 370 | */ 371 | 372 | table { 373 | border-collapse: collapse; 374 | border-spacing: 0; 375 | } --------------------------------------------------------------------------------