新闻详情

新闻详情

首页 / 资讯中心 / 详情

JSON:API Cursor Pagination 规范详解:page[size]、page[after] 与 page[before] 的完整实现指南

发布时间:2026/9/27 21:14:45来源:尧图网络
JSON:API Cursor Pagination 规范详解:page[size]、page[after] 与 page[before] 的完整实现指南
后端API设计【免费下载链接】json-apiA specification for building JSON APIs项目地址https://gitcode.com/gh_mirrors/js/json-api点击查看免费下载本文以 JSON:API 官方仓库中的 Cursor Pagination 配置文件 为骨架系统讲解光标cursor/keyset分页在 JSON:API 中的完整语义三个分页查询参数page[size]、page[after]、page[before]的取值规则与组合方式、响应文档中links与meta.page的结构约定、以及与排序?sort交互时的强制约束和四种标准错误响应。读完本文你将能依据这份规范在自己的 JSON:API 服务端实现前后向分页、范围分页与分页链接生成或在客户端正确解析和遍历分页响应。为什么选择光标分页与 offset–limit 的对比Cursor-based pagination又称 keyset pagination是一种被 Slack、Citus Data 等大规模 API 广泛采用的分页策略它规避了传统 offset–limit 分页的多个缺陷。该配置文件的开头部分专门阐述了这一背景见 _profiles/ethanresnick/cursor-pagination/index.md。在 offset–limit 分页中如果客户端翻页期间有前置页面的数据被删除后续结果会整体前移一位导致客户端请求下一页时跳过一条永远不会再看到的数据反之如果翻页期间有结果被新增进列表客户端可能在不同页面上重复看到同一条数据。光标分页可以同时避免这两种情况——因为它是基于最后一条已见数据的位置标记而非从第 N 条开始取来定位下一页。此外在大多数实现中光标分页对大数据集的表现也优于 offset–limit服务器不需要为了跳过前面 N 条而扫描并丢弃大量记录而是可以直接通过索引条件例如WHERE id ?定位到光标之后的数据。注JSON:API 1.1 基础规范本身对分页策略是中立的。在 _format/1.1/index.md 的 Pagination 一节中明确说明服务器可以使用page查询参数族query parameter family采用任何策略——例如基于页码的策略可用page[number]/page[size]而基于光标的策略可用page[cursor]。本配置文件则是在此基础上为光标策略规定了精确且可互操作的语义。三个核心查询参数本配置规范定义了三个查询参数来支撑光标分页参数作用必须性page[size]客户端期望在响应中看到的结果数量可选省略时由服务器决定默认值page[after]取该光标之后的下一页数据可选与page[before]可同时使用page[before]取该光标之前的上一页数据可选与page[after]可同时使用例如下面的请求取光标abcde之后的 100 个人GET /people?page[size]100page[after]abcde把page[after]换成page[before]即可向后翻页。而下面的请求则是取光标abcde与fghij之间两端均不包含的所有人GET /people?page[after]abcdepage[before]fghijpage[size]页面大小控制page[size]表示客户端希望在响应中出现的结果条数。它的约束如下必须为正整数。从技术上讲URI 查询参数的值始终是字符串因此规范在文末注释见配置文件的 Notes 部分中精确规定该值必须匹配正则^[0-9]$然后被解释为十进制整数。如果page[size]是负数、0 或非数字服务器必须按 invalid query parameter error无效查询参数错误的规则响应。服务器可以定义最大页面大小max page size。对每个支持分页的端点服务器可以设定单次响应最多返回多少条结果若未设定则隐式为无穷大。当page[size]超过该上限时服务器必须按 max page size exceeded error页面大小超限错误的规则响应。省略时的默认值。如果page[size]被省略服务器必须选择一个默认页面大小default page size该值必须是 1 到最大页面大小之间的整数含两端。规范定义了一个重要的术语——实际使用的页面大小used page size即page[size]的值若省略则取默认页面大小。任何合法的分页请求返回的分页条目pagination items数量必须等于实际使用的页面大小——前提是结果列表中至少有这么多条目且这些条目满足page[after]/page[before]的约束。换言之只有列表不够长时响应才会少于 used page size 条。page[after] 与 page[before]前后向翻页page[after]与page[before]都是可选参数取值都必须是合法的光标字符串若取值不是合法光标服务器必须按无效查询参数错误的规则响应。二者配合page[size]的语义如下当提供page[after]时返回的分页数据paginated data的第一项必须是结果列表中紧跟在光标之后的那一项。若光标之后没有更多条目则返回的分页数据必须是空数组。当提供page[before]时返回的分页数据最后一项必须是未分页结果列表中最接近光标、但仍位于光标之前的那一项。同理若光标之前没有条目返回空数组。注意page[before]的语义是以光标之前离光标最近的一项作为本页的末项因此本页中间项的数量由 used page size 控制页面整体是从前往后排列的见下方示例中的data顺序。示例数据规范用一组示例数据贯穿全文。假设被分页的结果列表为[ { type: examples, id: 1 }, { type: examples, id: 5 }, { type: examples, id: 7 }, { type: examples, id: 8 }, { type: examples, id: 9 } ]设光标xxx落在id: 9这一项上光标abcde落在id: 5这一项上。向后翻页示例请求GET /example-data?page[after]abcdepage[size]2响应为{ links: { prev: /example-data?page[before]yyypage[size]2, next: /example-data?page[after]zzzpage[size]2 }, data: [ // 每个条目的分页元数据是可选的 { type: examples, id: 7, meta: { page: { cursor: yyy } } }, { type: examples, id: 8, meta: { page: { cursor: zzz } } } ] }即从abcdeid5之后紧邻的 id7 开始取 2 条7、8。响应中的links.prev/links.next给出了前后向翻页的 URI。向前翻页示例请求GET /example-data?page[before]xxxpage[size]3响应为{ links: { prev: /example-data?page[before]abcdepage[size]3, next: /example-data?page[after]zzzpage[size]3 }, data: [ // 每个条目的分页元数据同样是可选的 { type: examples, id: 5 }, { type: examples, id: 7 }, { type: examples, id: 8 } ] }可以看到page[before]xxx使响应的末项为id: 8id9 是落在光标上的项不在返回范围内而id: 8之前的条目数量5、7 两条 8 自身共 3 条由 used page size 决定。省略两个光标参数如果请求既不带page[after]也不带page[before]则返回的分页数据必须从结果列表的第一项开始若结果列表为空分页数据必须是空数组。这等价于首页此时页面大小仍由page[size]或默认值决定。组合 page[after] 与 page[before]范围分页客户端可以在同一次请求中同时使用page[after]与page[before]这称为范围分页请求range pagination request——客户端要求从page[after]光标之后紧邻的一项开始一直到page[before]光标为止的全部结果。范围分页的额外规则服务器并非必须支持这种请求。若服务器选择不支持必须按 range pagination not supported error范围分页不支持错误的规则响应。在范围分页请求中服务器必须以该端点的最大页面大小作为默认页面大小——也就是说used page size 要么是page[size]的值要么是最大页面大小。如果同时满足page[after]与page[before]约束的结果条数超过了 used page size服务器必须像未提供page[before]那样返回相同的分页数据即从 after 光标开始截取一页但必须额外在分页元数据pagination metadata中加入rangeTruncated: true以告知客户端分页数据并未包含其请求的全部结果。示例一未截断假设最大页面大小大于 1请求GET /example-data?page[after]abcdepage[before]xxx响应为{ links: { prev: /example-data?page[before]yyy, next: /example-data?page[after]zzz }, data: [ { type: examples, id: 7 }, { type: examples, id: 8 } ] }示例二被截断若服务器最大页面大小为 1或客户端请求中带了page[size]1则响应为{ meta: { page: { rangeTruncated: true } }, links: { prev: /example-data?page[before]yyypage[size]1, next: /example-data?page[after]yyypage[size]1 }, data: [ { type: examples, id: 7 } ] }这里meta.page.rangeTruncated true明确告诉客户端7 与 8 之间还有一个结果没有返回客户端可以继续跟随next链接获取剩余部分。排序要求分页的前提是稳定有序分页只适用于有序的结果列表。该顺序在两次请求之间不得变化除非底层数据本身发生变化否则结果会在页面之间随意漂移。如果客户端的请求带有?sort参数但该排序只对结果产生部分排序例如?sortage多个人的 age 相同则服务器若想对该数据支持分页必须在客户端请求的排序约束之上追加额外的排序约束以产生唯一的总序。例如客户端请求GET /people?sortagepage[size]10服务器应当像客户端实际请求的是?sortage,id一样处理——用某个唯一字段或字段组合作为排序键的兜底。如果被分页的集合本身没有自然顺序、客户端也未请求排序例如关系relationship中的资源标识符对象集合服务器若想支持分页必须自行指定一个顺序。服务器可以拒绝分页请求如果客户端请求的排序方式导致服务器无法高效分页此时必须按 unsupported sort error不支持的排序错误的规则拒绝请求。Cursor光标的本质光标是由服务器用任意方式生成的一个字符串它把结果列表划分为三部分位于光标之前的结果、位于光标之后的结果以及可选的恰好落在光标上的某一条结果。以示例列表为例服务器可以用光标字符串abcde编码id 5。那么第一条结果id1在光标之前第二条id5落在光标上其余结果在光标之后。值得强调的是结果列表可以在客户端的两次分页请求之间变化。例如 id5 的资源被删除后光标abcde不再落在任何一条结果上但光标之前/之后的结果集合仍然稳定。这正是光标分页对翻页期间数据变化不敏感的关键。少数情况下服务器可能无法接受结果列表在请求之间变化例如需要严格一致的会话视图。此时服务器可以把唯一标识客户端或其会话的信息编码进光标中并基于该标识从某个时间点的快照返回一致的结果。文档结构与术语体系本规范对响应文档中的分页相关元素给出了精确的术语定义便于服务端与客户端实现者对齐语言术语定义paginated data分页数据响应文档中存放从完整结果列表中提取出来的结果的数组总是某个data键的值。当分页作用于顶层主数据时它是文档顶层data的值当分页作用于某个关系中的资源标识符对象时它是关系对象中data键的值pagination links分页链接作为分页数据的兄弟节点出现的links对象pagination metadata分页元数据作为分页数据和分页链接的兄弟节点出现的meta对象中的page成员pagination item分页条目分页数据数组中的一项pagination item metadata分页条目元数据分页数据项顶层meta对象中的page成员下面这段完整的标注示例请求GET /people?page[size]1展示了这些术语在文档中的实际位置{ // 分页链接对应顶层 data links: { }, meta: { // 分页元数据 page: {} }, // 分页数据 data: [ // 一个分页条目 { type: people, id: 1, // 分页条目元数据位于 page 中 meta: { page: {} }, attributes: {}, relationships: { friends: { // 分页链接。 // 实际中这里通常非空以表示服务器选择了对该关系进行分页 // 即使客户端并未显式请求——这是被允许的。 links: {}, // 另一处分页数据 data: [ // 一个分页条目带空的分页条目元数据 { type: people, id: 3, meta: { page: { } } } ] } } } ] }page 元数据对象成员本规范在 JSON:API 定义的每一个meta对象中都保留了page成员这些page成员各构成该配置规范定义的一个元素因此可以被别名化aliased。page成员出现时其值必须是一个对象任何其他类型的值都被视为未识别值unrecognized。各page对象中可识别的键/值在本文档各处定义。关于配置文件机制根据 _format/1.1/index.md 的定义Profile 用于在实现之间共享对规范的一种特定用法可以规定实现语义但不能修改、增删规范语义。客户端与服务器通过媒体类型参数profile来声明使用某个 Profile其值为以空格分隔的 Profile URI 列表。本光标分页 Profile 的最低 JSON:API 版本要求为 1.0minimum_jsonapi_version: 1.0它在仓库中归属到 Pagination 类别下见 _config.yml 的profile_categories配置并且整个profiles集合会以permalink: /:collection/:path的形式发布为独立页面见 _config.yml。条目光标Item Cursors服务器可以选择把部分或全部分页条目连同其cursor成员一起返回给客户端该成员出现在分页条目的元数据pagination item metadata中。若存在该成员必须持有一个在响应时刻落在该条目上的光标。客户端可以用它从这个条目继续翻页。例如{ // 顶层 links、meta 等省略 data: [{ type: people, id: 3, meta: { page: { cursor: someOpaqueString } } }] }有了这个响应客户端就可以用page[before]someOpaqueString或page[after]someOpaqueString从 person 3 开始向任意方向翻页——这正是从任意条目继续分页的通用实现方式。分页链接Pagination Links的生成规则JSON:API 基础规范允许四种分页链接prev、next、first和last。本配置文件在此基础上规定了更精确的生成与置空规则RECOMMENDED当first与last链接计算成本低廉时服务器应包含它们。MUST服务器必须为响应中的每一处分页数据都包含prev和next链接。若请求中不含page[before]参数服务器必须判断是否存在下一页若不存在则把next置为null。若请求中不含page[after]参数服务器必须判断是否存在上一页若不存在则把prev置为null。其他情况下当服务器能低成本地确定当前响应分别是第一页或最后一页时SHOULD相应地把prev或next置为null。廉价探底技巧当服务器难以确定是否存在上一页/下一页时可以在这些链接中使用一个会返回空数组分页数据的 URI。例如请求GET /example-data?page[before]xyz时服务器为了满足请求通常只会查询光标之前的记录因而并不清楚光标之后是否还有结果也没有廉价途径获知。此时服务器可以返回一个nextURI其中page[after]被设置为响应中末条分页条目的条目光标。客户端去抓取这个链接时要么拿到空数组从而知道自己已经到达末尾要么拿到后续结果。规范注记一般而言使用page[after]时服务器判断是否存在上一页更昂贵使用page[before]时判断是否存在下一页更昂贵。幸运的是使用page[after]的客户端通常只关心下一页反之亦然因此允许服务器返回一个可能对应空页面的链接常常可以省下一次永远不会被用到的查询。集合大小total 与 estimatedTotal分页元数据meta.page可以包含total成员其值为整数表示被分页的结果列表中的条目总数。例如对GET /people?page[size]2的响应可以包含{ meta: { page: { total: 200 } }, // links 省略 data: [ { type: people }, { type: people } ] }分页元数据还可以包含estimatedTotal成员。若存在其值必须是一个对象该对象可以有一个键bestGuess若存在则必须包含一个整数表示服务器对完整结果列表大小的最佳估计。当精确计算 total 成本过高时服务器可以选择使用estimatedTotal代替total。错误场景与标准错误响应本规范定义了四种错误场景全部以 HTTP 400 Bad Request 响应且都遵循 JSON:API 的错误对象error object规范source指向出问题的查询参数links.type指向该 Profile 下的错误类型 URI。不支持的排序错误Unsupported Sort Error当客户端请求的排序方式服务器无法高效分页时使用。响应为400 Bad Request错误对象必须把sort参数标识为错误的source并带有type链接https://jsonapi.org/profiles/ethanresnick/cursor-pagination/unsupported-sort仓库中对应的错误文档页通过redirect_to重定向到 index.md 的 Sorting Requirement 小节。页面大小超限错误Max Page Size Exceeded Error当page[size]超过服务器为该端点定义的最大页面大小时使用。响应为400 Bad Request错误对象必须把page[size]标识为source在错误对象meta对象的page元素中以整数maxSize提供最大页面大小并带有type链接https://jsonapi.org/profiles/ethanresnick/cursor-pagination/max-size-exceeded如果本 Profile 的page元素未被别名化错误对象大致如下{ status: 400, meta: { page: { maxSize: 100 } }, title: Page size requested is too large., detail: You requested a size of 200, but 100 is the maximum., source: { parameter: page[size] }, links: { type: [https://jsonapi.org/profiles/ethanresnick/cursor-pagination/max-size-exceeded] } }仓库中对应的错误文档页重定向到 index.md 中的 Max Page Size 相关小节。无效查询参数错误Invalid Parameter Value Error当page[size]不是正整数、或page[after]/page[before]的值不是合法光标时使用。响应为400 Bad Request错误对象在source成员中标识有问题的参数。例如{ errors: [{ title: Invalid Parameter., detail: page[size] must be a positive integer; got 0, source: { parameter: page[size] }, status: 400 }] }范围分页不支持错误Range Pagination Not Supported Error当服务器不支持同时使用page[after]与page[before]的范围分页请求时使用。响应为400 Bad Request错误对象必须带有type链接https://jsonapi.org/profiles/ethanresnick/cursor-pagination/range-pagination-not-supported仓库中对应的错误文档页重定向到 index.md 中的范围分页小节。服务端实现要点小结把上述规范收敛为服务端实现清单可以归纳为以下要点排序先行为每个可分页端点确定一个稳定唯一的总序必要时在客户端?sort之后追加唯一键兜底这是分页成立的前提。光标生成用自己的方式把排序键编码为不透明字符串可同时把客户端/会话标识编码进光标以支持快照语义。参数校验page[size]必须匹配^[0-9]$且为正整数page[after]/page[before]必须是合法光标否则返回 400 与对应错误对象。页面大小裁决确定 used page size显式page[size]或默认值范围分页时默认取 max page size超限返回 max-size-exceeded 错误。数据切片按page[after]/page[before]定位切片起点/终点返回至多 used page size 条范围分页被截断时置meta.page.rangeTruncated true。链接生成每处分页数据都给出prev/next低成本可判定的边界置null否则可返回空页面兜底的 URI低成本时附带first/last。可选增强在条目级meta.page.cursor返回条目光标在集合级meta.page返回total或estimatedTotal。这套规范既保留了光标分页对数据增删的天然免疫能力又通过严格的 MUST/SHOULD/MAY 语义为服务端与客户端提供了可互操作的契约——无论你是在实现服务端切片逻辑还是在编写客户端翻页遍历器Cursor Pagination 配置文件 都是直接的实现依据。赞分享后端API设计【免费下载链接】json-apiA specification for building JSON APIs项目地址https://gitcode.com/gh_mirrors/js/json-api点击查看免费下载相关推荐CmdPal 参数页Parameters Page扩展 SDK 设计规范与实现指南CmdPal 参数页Parameters Page扩展 SDK 设计规范与实现指南 IParametersPage 是 Microsoft PowerToy桌面应用开发工具TradingView策略回测数据交给AI解读三步拿到指标、交易明细与权益曲线TradingView策略回测数据交给AI解读三步拿到指标、交易明细与权益曲线 tradingview mcp 是一个 AI 辅助的 TradingViewMCP 服务AI 应用人工智能金融科技CLIHugo 页面种类Page Kind完全指南从 home、page、section 到 taxonomy 与 termHugo 页面种类Page Kind完全指南从 home、page、section 到 taxonomy 与 term 导读 页面种类page kind开发工具前端CLI上一篇SourceGit v2025.09版本发布Git客户端工具的全面升级下一篇Apache Ignite内存与JVM调优指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

Codex CLI 频繁 Reconnecting 怎么排查?先分清 WebSocket、SSE 与配置边界 2026/9/28 6:02:22

Codex CLI 频繁 Reconnecting 怎么排查?先分清 WebSocket、SSE 与配置边界

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
音视频基础知识-RGB色彩空间 2026/9/28 6:02:22

音视频基础知识-RGB色彩空间

在数字化时代,RGB色彩空间成为了音视频领域的核心。每当我们观看电视、使用电脑或智能手机,RGB色彩空间都在背后默默地塑造着我们的视觉体验。它不仅是图像和视频处理的基础,更是现代显示技术的关键组成部分。 在这篇文章中,我们将深入探讨RGB色彩空间,理解其原理和在音视…

阅读更多 →
万网cname域名解析避坑指南:新手建站防坑实战 2026/9/28 6:02:22

万网cname域名解析避坑指南:新手建站防坑实战

万网cname域名解析避坑指南:新手建站防坑实战 找建站公司最怕什么?不是功能少,是后期运维被坑。域名解析配置错一个CNAME,网站打不开,对方收你几千块“紧急修复费”,这钱花得冤枉。这篇避坑指南,专为转行做网站的新手整理,结合阿里云官方文…

阅读更多 →
从打孔卡到云计算:读计算机发展史,看透技术选型的底层逻辑 2026/9/28 6:02:22

从打孔卡到云计算:读计算机发展史,看透技术选型的底层逻辑

做了十几年开发,回头再读计算机的发展历程,我发现最值钱的不是记住哪一年发明了什么机器,而是理解一个朴素的逻辑:计算能力的每一次跨越,都是人类在“算得更快”和“算得起”之间不断打破瓶颈的结果。刚入行那几年&…

阅读更多 →
电脑维修工具箱实战:蓝屏DMP分析、驱动卸载与系统修复全流程 2026/9/28 6:02:22

电脑维修工具箱实战:蓝屏DMP分析、驱动卸载与系统修复全流程

先说说我这几年的工作背景。我长期在电脑售后和系统维护一线,每天打交道最多的就是蓝屏、卸载不干净、系统文件损坏、开机引导丢失这类“疑难杂症”。客户把机器送过来,往往都补了一句“我在家自己整了好几天,实在没办法了”。其实很多问题并…

阅读更多 →
ESP32 MicroPython下用ESP-NOW实现双向通信:从原理到踩坑 2026/9/28 6:02:16

ESP32 MicroPython下用ESP-NOW实现双向通信:从原理到踩坑

前阵子做一个小项目,需要让两块ESP32在不接路由器的情况下互相传数据。一开始想着用WiFiMQTT,结果发现没有AP根本玩不转;后来又试BLE,配对流程和连接管理把人折腾得够呛。直到朋友提醒了一句"你试试ESPNOW",…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉