| RFC 9535 | JSONPath | 2024 年 2 月 |
| Gössner 等 | 标准路线 | [页] |
JSONPath 定义了一种字符串语法,用于从给定的 JSON 值中选择并提取 JSON(RFC 8259)值 。¶
这是一份互联网标准路线文档。¶
本文档是互联网工程任务组 (IETF)的产物。它代表 IETF 社区的共识。它已经 过公开审查,并已获互联网工程指导组(IESG)批准发布。 关于互联网标准的更多信息可见 RFC 7841 第 2 节。¶
关于本文档当前状态、任何 勘误以及如何提供反馈的信息,可在 https://www.rfc-editor.org/info/rfc9535 获取。¶
Copyright (c) 2024 IETF Trust and the persons identified as the document authors. All rights reserved.¶
This document is subject to BCP 78 and the IETF Trust's Legal Provisions Relating to IETF Documents (https://trustee.ietf.org/license-info) in effect on the date of publication of this document. Please review these documents carefully, as they describe your rights and restrictions with respect to this document. Code Components extracted from this document must include Revised BSD License text as described in Section 4.e of the Trust Legal Provisions and are provided without warranty as described in the Revised BSD License.¶
JSON [RFC8259] 是一种流行的 结构化数据值 表示格式。 JSONPath 定义了一种字符串语法,用于从给定的 JSON 值中 选择并提取 JSON 值。¶
相对于 JSON Pointer [RFC6901],JSONPath 并非旨在作为替代品,而是作为一个 更强大的 配套工具。见附录 C。¶
本文档中的关键词“MUST”、“MUST NOT”、“REQUIRED”、“SHALL”、“SHALL NOT”、“SHOULD”、“SHOULD NOT”、“RECOMMENDED”、“NOT RECOMMENDED”、 “MAY”和“OPTIONAL” 应按照 BCP 14 [RFC2119] [RFC8174] 中的描述进行解释,但仅当它们以全大写形式出现时,如此处所示。¶
本文档中的语法规则应按 ABNF 解释,
如 [RFC5234] 中所述。
本文档中的 ABNF 终端值定义的是 Unicode 标量值,而不是
其 UTF-8 编码。
例如,Unicode PLACE OF INTEREST SIGN(U+2318)在 ABNF 中可定义为
%x2318。¶
函数使用函数名后跟一对括号来引用,
例如 fname()。¶
除非下文另有澄清,采用 [RFC8259] 的术语。 术语“primitive”和“structured”用于按照 [RFC8259] 的 第 1 节 对 不同种类的值进行分组。JSON 对象和数组是 结构化的;所有其他值都是基本的。 “object”、“array”、“number”和“string”的定义保持 不变。 重要的是,尤其是“object”和“array”并不具有 通用含义,例如它们在一般编程语境中可能具有的含义。¶
本文档中使用的其他术语定义如下。¶
按照 [RFC8259],符合 JSON 通用数据模型的数据项,即 基本数据(数字、文本字符串以及特殊 值 null、true 和 false),或结构化数据(JSON 对象和数组)。 [RFC8259] 侧重于 JSON 值的文本 表示,并且 没有完整定义此处假定的值抽象。¶
对象中的名称/值对。(成员本身不是 值。)¶
构成成员的名称/值对中的名称(一个字符串)。 这也在 [RFC8259] 中使用, 但该规范并未 正式定义它。 此处包含该术语是为了完整性。¶
JSON 数组中的一个值。¶
标识数组中特定元素的整数。¶
JSONPath 表达式的简称。¶
JSONPath 表达式所应用到的值的简称。¶
值在查询实参中的位置。可以将其 看作一系列名称和索引,通过 查询实参中的对象和数组导航到该值,空序列 表示查询实参本身。 位置可以表示为规范化路径(定义如下)。¶
一个值及其在查询实参中的位置所组成的对。¶
其值为整个查询实参的唯一节点。¶
表达式 $,它引用查询实参的根节点。¶
表达式 @,它在过滤器表达式求值的
语境中引用当前节点(稍后描述)。¶
如果该节点是数组,则为其元素的节点;如果该节点 是对象,则为其成员值的节点。 如果该节点既不是数组也不是对象,则它没有子节点。¶
该节点的子节点,以及其 子节点的子节点,如此递归。 更形式化地说,节点之间的“后代”关系是“子节点” 关系的传递闭包。¶
该节点在该值中的祖先数量。该值的根 节点深度为零, 根节点的子节点深度为一,它们的子节点深度为二,依此类推。¶
节点的列表。 虽然节点列表可以用 JSON 表示,例如表示为数组,但本文档 不要求也不假定任何特定表示。¶
形式参数(函数的),可在函数表达式中 接受函数实参 (实际参数)。¶
JSONPath 表达式的一种形式,它通过 提供一个查询来标识值中的某个节点,该查询的结果正好是该节点。 查询实参中的每个节点都由恰好一个规范化路径标识 (我们说该规范化路径对该节点是“唯一”的),并且要成为特定查询实参的 规范化路径,该规范化路径需要标识 恰好一个节点。这类似于 JSON Pointer [RFC6901],但语法上不同。 注:此定义基于第 2.7 节中的语法定义; 能够标识值中某个节点但不符合该语法的 JSONPath 表达式不是规范化路径。¶
除高代理和 低代理码点之外的任何 Unicode [UNICODE] 码点(换言之,十六进制闭区间 0 到 D7FF 或 E000 到 10FFFF 中的整数)。JSONPath 查询是 Unicode 标量值序列。¶
用于选择输入值的子节点
([<selectors>])
或后代(..[<selectors>])的构造之一。¶
段内的单个项,它接受输入值并 产生一个由输入值的子节点组成的节点列表。¶
由在语法上以某种方式 受限的段构建的 JSONPath 表达式(第 2.3.5.1 节),从而无论输入 值为何,该表达式都会产生包含至多一个节点的节点列表。 注:总是产生单一节点列表但不符合 第 2.3.5.1 节中语法的 JSONPath 表达式不是单一查询。¶
本文档将查询实参建模为一棵 JSON 值树, 每个 JSON 值 都有自己的节点。 节点要么是根节点,要么是其后代之一。¶
本文档将把查询应用于 查询实参的结果建模为节点列表(节点的列表)。¶
节点是查询实参中可选择的部分。 对象中唯一可由查询选择的部分是 成员值。成员名和成员(名称/值对)不能被 选择。 因此,成员值有节点,但成员和成员名没有。 类似地,成员值是对象的子节点,但成员和 成员名不是。¶
本文档基于 Stefan Gössner 广受欢迎的 JSONPath 提案(日期为 2007-02-21)[JSONPath-orig],建立在其实现被广泛 部署所积累的经验之上,并为其提供规范性规范。¶
附录 B 描述了 JSONPath 如何受到 XML 的 XPath [XPath] 启发。¶
JSONPath 原本旨在作为 JSON 在 PHP 和 JavaScript 等编程语言中的
实现的轻量级配套工具,
因此不像 XPath 那样定义自己的表达式语言,
JSONPath 将查询的某些部分委托给底层
运行时,例如 JavaScript 的 eval() 函数。
随着 JSONPath 在更多环境中实现,JSONPath
表达式的可移植性逐渐降低。
例如,正则表达式处理通常委托给
便捷的正则表达式引擎。¶
本文档旨在去除此类特定于实现的依赖,并 作为一个可跨 编程语言和环境使用的通用 JSONPath 规范。 这意味着并不总是能 实现向后兼容;本文档的一项设计原则是, 在不危及获得可用、稳定 JSON 查询语言这一目标的前提下, 即使实现之间的“共识”并不完美,也采用这种“共识”。¶
之所以选择术语 JSONPath,是因为它受 XPath 启发, 也因为查询结果由标识 JSON 查询实参中节点的 路径 组成。¶
JSONPath 查询所应用到的 JSON 值,按定义,是一个 有效的 JSON 值。JSON 值通常通过解析 JSON 文本构造。¶
将 JSON 文本解析为 JSON 值,以及当 JSON 文本不表示有效 JSON 时会发生什么,不由本文档定义。 [RFC8259] 的第 4 节和第 8 节指出了可能 符合 JSON 文本语法但不属于可互操作使用的 特定情形,因为它们可能导致不可预测的行为。 本文档不试图为这些情形下的 JSONPath 查询定义可预测 行为。¶
具体而言,第 2.3.1、2.3.2、 2.3.5 和 2.5.2 节的“语义”小节描述的行为, 在所考虑的某个对象的 JSON 值 是由呈现如下情况的 JSON 文本构造时会变得不可预测: 单个对象中存在多个共享同一成员名的成员 (“重复名称”;见[RFC8259] 的 第 4 节)。 此外,在按名称选择子节点(第 2.3.1 节)和比较字符串 (第 2.3.5.2.2 节)时,假定这些 字符串是 Unicode 标量值序列;如果不是,则行为会变得不可预测 ([RFC8259] 的 第 8.2 节)。¶
JSONPath 表达式应用于一个 JSON 值,该值称为查询实参。 输出是节点列表。¶
JSONPath 表达式由一个标识符后跟一系列 零个或多个段组成,每个段包含一个或多个选择器。¶
段选择输入值的子节点([<selectors>])或
后代(..[<selectors>])。¶
段可以使用 方括号表示法,例如:¶
$['store']['book'][0]['title']¶
也可以使用更紧凑的 点表示法,例如:¶
$.store.book[0].title¶
方括号表示法包含一个或多个(逗号分隔的)任意种类的 选择器。 选择器将在下一节详细说明。¶
JSONPath 表达式可以组合使用方括号表示法和点 表示法。¶
本文档将方括号表示法视为规范形式,并以 方括号表示法定义简写的点表示法。示例和描述会在方便时使用简写。¶
名称选择器,例如 'name',选择对象的具名子节点。¶
索引选择器,例如 3,选择数组的带索引子节点。¶
在表达式 [*] 中,通配符 *(第 2.3.2 节)选择
一个节点的所有子节点;在表达式 ..[*] 中,它选择一个节点的所有后代。¶
数组切片 start:end:step(第 2.3.4 节)从数组中选择一系列
元素,给定起始位置、结束位置以及
一个可选的步长值,用于使位置从起点移动到
终点。¶
过滤器表达式 ?<logical-expr> 选择
对象或数组的某些子节点,如下所示:¶
$.store.book[?@.price < 10].title¶
| 语法元素 | 描述 |
|---|---|
$
|
根节点 标识符(第 2.2 节) |
@
|
当前节点 标识符(第 2.3.5 节)(仅在 过滤器选择器内有效) |
[<selectors>]
|
子段 (第 2.5.1 节):选择节点的零个或多个子节点 |
.name
|
['name'] 的简写
|
.*
|
[*] 的简写
|
..[<selectors>]
|
后代 段(第 2.5.2 节):选择 节点的零个或多个后代 |
..name
|
..['name'] 的简写
|
..*
|
..[*] 的简写
|
'name'
|
名称选择器 (第 2.3.1 节):选择对象的具名子节点 |
*
|
通配符 选择器(第 2.3.2 节):选择 节点的所有子节点 |
3
|
索引选择器 (第 2.3.3 节):选择数组中带索引的子节点(从 0 开始) |
0:100:5
|
数组切片选择器 (第 2.3.4 节):数组的 start:end:step |
?<logical-expr>
|
过滤器 选择器(第 2.3.5 节):使用 逻辑表达式选择特定子节点 |
length(@.foo)
|
函数扩展(第 2.4 节): 在过滤器表达式中调用函数 |
本节为资料性内容。它提供 JSONPath 表达式的示例。¶
这些示例基于 图 1 所示的简单 JSON 值,它表示一家书店 (其中还有一辆自行车)。¶
{ "store": {
"book": [
{ "category": "reference",
"author": "Nigel Rees",
"title": "Sayings of the Century",
"price": 8.95
},
{ "category": "fiction",
"author": "Evelyn Waugh",
"title": "Sword of Honour",
"price": 12.99
},
{ "category": "fiction",
"author": "Herman Melville",
"title": "Moby Dick",
"isbn": "0-553-21311-3",
"price": 8.99
},
{ "category": "fiction",
"author": "J. R. R. Tolkien",
"title": "The Lord of the Rings",
"isbn": "0-395-19395-8",
"price": 22.99
}
],
"bicycle": {
"color": "red",
"price": 399
}
}
}
表 2 展示了 可应用于此示例的一些 JSONPath 查询及其预期结果。¶
| JSONPath | 预期结果 |
|---|---|
$.store.book[*].author
|
商店中所有图书的作者 |
$..author
|
所有作者 |
$.store.*
|
商店中的所有东西,即 一些书和一辆红色自行车 |
$.store..price
|
商店中所有东西的价格 |
$..book[2]
|
第三本书 |
$..book[2].author
|
第三本书的作者 |
$..book[2].publisher
|
空结果:第三本书没有 “publisher”成员 |
$..book[-1]
|
按顺序的最后一本书 |
$..book[0,1]$..book[:2]
|
前两本书 |
$..book[?@.isbn]
|
所有带 ISBN 号的书 |
$..book[?@.price<10]
|
所有价格低于 10 的书 |
$..*
|
输入值中包含的所有成员值和数组元素 |
JSONPath 表达式是一个字符串,当应用于 JSON 值 (查询实参)时,它会选择该实参的零个或多个节点,并将 这些节点作为节点列表输出。¶
查询MUST使用 UTF-8 编码。 本文档给出的查询语法假定其 UTF-8 形式首先按照 [RFC3629] 中所述解码为 Unicode 标量值;能够得到等价 结果的实现方法也是可行的。¶
用作 JSONPath 查询的字符串需要是格式良好且 有效的。 如果字符串符合本文档中的 ABNF 语法,则它是格式良好的 JSONPath 查询。 如果格式良好的 JSONPath 查询还满足本文档提出的两个语义 要求,则它是有效的;这些要求如下:¶
JSONPath 实现对于任何不是 格式良好且有效的查询MUST引发错误。 JSONPath 查询的格式良好性和有效性独立于 该查询所应用到的 JSON 值。在将查询应用于值的过程中, 不能再引发与 JSONPath 查询的 格式良好性和有效性相关的错误。 这清楚地把查询中的格式良好性/有效性错误 与可能实际源自数据缺陷的不匹配区分开来。¶
有效查询所期望的结构 与数据中发现的结构之间的不匹配,可能导致空查询结果, 这可能出乎意料,并指示二者之一存在缺陷。 因此,JSONPath 实现可能希望向应用开发者提供诊断信息, 以帮助找出空 结果的原因。¶
显然,实现仍可能在执行 JSONPath 查询时失败,例如由于资源耗尽,但本文档不对此建模。 然而,实现MUST NOT 静默失常。具体而言,如果一个有效的 JSONPath 查询 针对某个结构化值求值,而该值的大小过大,以至于无法 正确处理该查询(例如需要处理超出精确值范围的 数字),则实现 MUST提供溢出指示。¶
(熟悉 HTTP 错误模型的读者,在思考格式良好性和有效性时, 可能会联想到 400 类错误;并可能会把资源耗尽及相关错误识别为可类比于 500 类 错误。)¶
从语法上看,JSONPath 查询由根标识符
($)组成,该标识符
代表一个包含查询实参根节点的节点列表,
后跟一个可能为空的段序列。¶
jsonpath-query = root-identifier segments
segments = *(S segment)
B = %x20 / ; Space
%x09 / ; Horizontal tab
%x0A / ; Line feed or New line
%x0D ; Carriage return
S = *B ; optional blank space
¶
在本文档中,JSONPath 查询的语义定义了 所要求的结果,而不规定实现的内部工作方式。 本文档可以用过程化的逐步方式描述语义;然而, 这类描述仅在任何实现MUST产生相同结果这一意义上是规范性的, 而不是要求实现者使用相同算法。¶
语义是:有效查询针对一个值 (查询实参)执行,并产生一个节点列表(即该值的零个或多个 节点组成的列表)。¶
查询是一个根标识符后跟零个或 多个段的序列,其中每个段都应用于前一个根标识符或段的结果, 并为下一个段提供输入。 这些结果和输入都采用节点列表的形式。¶
根标识符产生的节点列表包含单个 节点 (即查询实参)。 最后一个段产生的节点列表作为查询 结果呈现。根据具体 API,它可能 呈现为节点处 JSON 值的数组、引用这些节点的 规范化路径数组,或二者兼有——也可以是实现所需的 某种其他表示。 注:空节点列表是有效的查询结果。¶
段依次作用于其输入节点列表中的每个节点, 并按派生这些结果的输入 节点列表顺序,将所得节点列表连接起来,以产生 该段的结果。一个节点可以被选择多次,并在节点列表中出现 相应次数。重复节点不会被移除。¶
语法有效的段在执行查询时MUST NOT 产生错误。 这意味着一些可能被认为是错误的 操作,例如使用超出数组范围的索引, 只会导致选择更少的节点。 (关于此属性的更多讨论可见第 2.1 节引言。)¶
这种方法的一个结果是,如果任一段 产生空节点列表,则整个查询也会产生空 节点列表。¶
如果查询的语义给实现留下了 产生多种可能排序的选择,则某个特定实现 可以在连续多次运行该查询时产生不同的排序。¶
考虑这个示例。给定查询实参
{"a":[{"b":0},{"b":1},{"c":2}]},
查询 $.a[*].b 选择以下节点列表(此处用它们的
值表示):0、1。¶
该查询由 $ 后跟三个段组成:
.a、[*] 和 .b。¶
首先,$ 产生一个仅由
查询实参组成的节点列表。¶
接下来,.a 从任何对象输入节点中选择,
并选择输入
节点中与成员名 "a" 相对应的任何
成员值的节点。
结果仍是一个包含单个节点的列表:
[{"b":0},{"b":1},{"c":2}]。¶
接下来,[*] 从输入数组节点中选择
所有元素。
结果是三个节点的列表:{"b":0}、{"b":1} 和
{"c":2}。¶
最后,.b 从任何具有成员名
b 的对象输入节点中选择,并选择输入节点中与该名称相对应的
成员值节点。
结果是包含 0、1 的列表。
这是三个列表的连接:两个长度为一的列表分别包含
0 和 1,以及一个长度为零的列表。¶
选择器只出现在 子段(第 2.5.1 节)和 后代段(第 2.5.2 节)内。¶
选择器会产生一个由输入值的零个或多个子节点组成的节点列表。¶
有多种选择器可产生对象的子节点、 数组的子节点, 或对象和数组二者的子节点。¶
selector = name-selector /
wildcard-selector /
slice-selector /
index-selector /
filter-selector
¶
每种选择器的语法和语义定义如下。¶
名称选择器 '<name>' 至多选择
一个对象成员值。¶
与 JSON 不同, JSONPath 语法允许字符串用单引号或 双引号括起来。¶
name-selector = string-literal
string-literal = %x22 *double-quoted %x22 / ; "string"
%x27 *single-quoted %x27 ; 'string'
double-quoted = unescaped /
%x27 / ; '
ESC %x22 / ; \"
ESC escapable
single-quoted = unescaped /
%x22 / ; "
ESC %x27 / ; \'
ESC escapable
ESC = %x5C ; \ backslash
unescaped = %x20-21 / ; see RFC 8259
; omit 0x22 "
%x23-26 /
; omit 0x27 '
%x28-5B /
; omit 0x5C \
%x5D-D7FF /
; skip surrogate code points
%xE000-10FFFF
escapable = %x62 / ; b BS backspace U+0008
%x66 / ; f FF form feed U+000C
%x6E / ; n LF line feed U+000A
%x72 / ; r CR carriage return U+000D
%x74 / ; t HT horizontal tab U+0009
"/" / ; / slash (solidus) U+002F
"\" / ; \ backslash (reverse solidus) U+005C
(%x75 hexchar) ; uXXXX U+XXXX
hexchar = non-surrogate /
(high-surrogate "\" %x75 low-surrogate)
non-surrogate = ((DIGIT / "A"/"B"/"C" / "E"/"F") 3HEXDIG) /
("D" %x30-37 2HEXDIG )
high-surrogate = "D" ("8"/"9"/"A"/"B") 2HEXDIG
low-surrogate = "D" ("C"/"D"/"E"/"F") 2HEXDIG
HEXDIG = DIGIT / "A" / "B" / "C" / "D" / "E" / "F"
¶
注:¶
Double-quoted 字符串遵循 JSON 字符串语法([RFC8259] 的 第
7 节);
single-quoted 字符串遵循类似模式。
本规范未尝试改进此语法,因此,如果希望
转义标量值高于 0xFFFF 的字符,例如 U+1F041(“🁁”,
DOMINO TILE HORIZONTAL-02-02),
它们需要用一对代理转义表示
(本例中为 "\uD83C\uDC41")。¶
\u 转义中的每个十六进制数字(如
hexchar 引用的规则所指定)可以是小写或大写,
而 \u 中的 u 需要是小写(表示为
%x75)。¶
name-selector 字符串MUST通过移除外围引号并
将每个转义序列替换为其等价 Unicode 字符,转换为
成员名 M,如
表 4 所示:¶
| 转义序列 | Unicode 字符 | 描述 |
|---|---|---|
\b
|
U+0008 | BS 退格 |
\t
|
U+0009 | HT 水平制表符 |
\n
|
U+000A | LF 换行 |
\f
|
U+000C | FF 换页 |
\r
|
U+000D | CR 回车 |
\"
|
U+0022 | 引号 |
\'
|
U+0027 | 撇号 |
\/
|
U+002F | 斜杠(solidus) |
\\
|
U+005C | 反斜杠(reverse solidus) |
\uXXXX
|
见 第 2.3.1.1 节 | 十六进制转义 |
将 name-selector 应用于对象节点时,
会选择其名称等于成员名 M 的成员值,
如果没有这样的成员值,则不选择任何内容。
对于不是对象的值,不会选择任何内容。¶
注:处理名称选择器需要将
成员名字符串 M
与该选择器所应用到的 JSON 中的成员名字符串进行比较。
两个字符串MUST被认为相等,当且仅当
它们是相同的
Unicode 标量值序列。换言之,在比较之前,
MUST NOT 对来自 JSONPath 的成员名字符串
M 或
JSON 中的成员名字符串应用规范化操作。¶
通配符选择器选择 对象或数组的所有子节点。 对象的子节点在所得节点列表中出现的顺序 未作规定, 因为 JSON 对象是无序的。 数组的子节点在所得节点列表中按数组顺序出现。¶
注意,对象的子节点是其成员值, 而不是其成员名。¶
通配符选择器不会从基本
JSON 值(即
数字、字符串、true、false 或
null)中选择任何内容。¶
JSON:¶
{
"o": {"j": 1, "k": 2},
"a": [5, 3]
}
¶
查询:¶
| 查询 | 结果 | 结果路径 | 注释 |
|---|---|---|---|
$[*]
|
{"j": 1, "k": 2} [5, 3]
|
$['o'] $['a']
|
对象值 |
$.o[*]
|
1 2
|
$['o']['j'] $['o']['k']
|
对象值 |
$.o[*]
|
2 1
|
$['o']['k'] $['o']['j']
|
另一种结果 |
$.o[*, *]
|
1 2 2 1
|
$['o']['j'] $['o']['k'] $['o']['k'] $['o']['j']
|
非确定性 排序 |
$.a[*]
|
5 3
|
$['a'][0] $['a'][1]
|
数组成员 |
上面使用查询 $.o[*, *] 的示例
表明,当通配符选择器应用于具有两个或更多
成员的对象节点时,它每次出现在子段中都可能产生顺序不同的
节点列表
(但当它应用于成员少于两个的对象节点或数组节点时不会如此)。¶
索引选择器 <index> 至多匹配
一个数组元素值。¶
index-selector = int ; decimal integer
int = "0" /
(["-"] DIGIT1 *DIGIT) ; - optional
DIGIT1 = %x31-39 ; 1-9 non-zero digit
¶
应用数值型 index-selector 会选择
对应的
元素。JSONPath 允许它为负数(见第
2.3.3.2 节)。¶
为保持有效,索引选择器值MUST位于 I-JSON 精确值范围内(见第 2.1 节)。¶
注:¶
数组切片选择器的形式为
<start>:<end>:<step>。
它匹配数组中从索引 <start> 开始、
到 <end> 结束(但
不包含该索引)的元素,同时按 step 递增;
默认值为 1。¶
slice-selector = [start S] ":" S [end S] [":" [S step ]] start = int ; included in selection end = int ; not included in selection step = int ; default: 1¶
切片选择器由三个可选的十进制 整数组成,以冒号分隔。 当第三个整数省略时,第二个冒号可以省略。¶
切片选择器的灵感来自 曾为 ECMAScript 4(ES4)提出但从未发布的切片运算符, 以及 Python 的切片运算符。¶
本节为资料性内容。¶
数组切片的灵感来自 JavaScript 语言中
Array.prototype.slice 方法的行为,
该行为由 ECMA-262 标准
[ECMA-262] 定义;
同时加入了 step 参数,其灵感
来自 Python 切片表达式。¶
数组切片表达式
start:end:step 选择从索引
start 开始、
按 step 递增、并以 end
结束(end 本身被排除)的元素。
因此,例如表达式 1:3(其中
step 默认为 1)
选择索引为 1 和 2 的元素(按该
顺序),而
1:5:2 选择索引为 1 和
3 的元素。¶
当 step 为负时,元素按
反向顺序选择。因此,
例如 5:1:-2 选择索引为
5 和 3 的元素(按
该顺序),而 ::-1 选择数组中的所有元素,
并按
反向顺序排列。¶
当 step 为 0 时,不选择
任何元素。
(这是与 Python 行为不同的唯一情况;Python 在这种情况下
会引发错误。)¶
下一节完整规定了该行为, 而不依赖于 JavaScript 或 Python 的行为。¶
切片表达式按与数组相同的顺序
或反向顺序,选择输入数组中
元素的一个子集,
具体取决于 step 参数的符号。
它不会从非数组节点中选择任何节点。¶
切片由两个切片参数
start 和 end,以及
一个迭代增量 step 定义。
这些参数均为
可选。在本节余下部分,len 表示
输入数组的长度。¶
step 的默认值为
1。
start 和 end 的默认值取决于
step 的符号,
如表 8所示。¶
| 条件 | start | end |
|---|---|---|
| step >= 0 | 0 | len |
| step < 0 | len - 1 | -len - 1 |
切片表达式参数 start
和 end 不能直接用作
切片边界,必须先被规范化。
此目的下的规范化定义为:¶
FUNCTION Normalize(i, len):
IF i >= 0 THEN
RETURN i
ELSE
RETURN len + i
END IF
¶
数组索引表达式
i 应用于长度为 len 的数组时的结果,
是数组切片表达式
Normalize(i, len):Normalize(i, len)+1:1 的结果。¶
切片表达式参数 start
和 end 用于推导切片边界 lower
和 upper。
由 step 符号定义的
迭代方向决定哪个参数是
下界,哪个
是上界:¶
FUNCTION Bounds(start, end, step, len):
n_start = Normalize(start, len)
n_end = Normalize(end, len)
IF step >= 0 THEN
lower = MIN(MAX(n_start, 0), len)
upper = MIN(MAX(n_end, 0), len)
ELSE
upper = MIN(MAX(n_start, -1), len-1)
lower = MIN(MAX(n_end, -1), len-1)
END IF
RETURN (lower, upper)
¶
切片表达式选择
下界与上界之间索引处的元素。
在以下伪代码中,a(i) 是数组 a 的第 i+1 个
元素
(即 a(0) 是第一个元素,a(1) 是
第二个元素,依此类推)。¶
IF step > 0 THEN
i = lower
WHILE i < upper:
SELECT a(i)
i = i + step
END WHILE
ELSE if step < 0 THEN
i = upper
WHILE lower < i:
SELECT a(i)
i = i + step
END WHILE
END IF
¶
当 step = 0 时,不选择
任何元素,结果数组为空。¶
JSON:¶
["a", "b", "c", "d", "e", "f", "g"]¶
查询:¶
| 查询 | 结果 | 结果路径 | 注释 |
|---|---|---|---|
$[1:3]
|
"b" "c"
|
$[1] $[2]
|
使用默认 步长的切片 |
$[5:]
|
"f" "g"
|
$[5] $[6]
|
没有结束 索引的切片 |
$[1:5:2]
|
"b" "d"
|
$[1] $[3]
|
步长为 2 的切片 |
$[5:1:-2]
|
"f" "d"
|
$[5] $[3]
|
负 步长切片 |
$[::-1]
|
"g" "f" "e"
"d" "c" "b" "a"
|
$[6] $[5] $[4]
$[3] $[2] $[1] $[0]
|
反向切片 |
过滤器选择器用于迭代结构化值的元素或成员, 即 JSON 数组和对象。 这些结构化值由使用过滤器选择器的 子段或后代段所提供的节点列表标识。¶
对于每次迭代(元素/成员),会对一个逻辑表达式(即 过滤器表达式) 进行求值,该表达式决定 元素/成员的节点是否被选择。 (虽然逻辑表达式求值结果在数学上是 布尔值,但本规范使用术语 logical,以保持它与 JSON 能表示的布尔值之间的 区别。)¶
在迭代过程中,过滤器表达式会接收 正在过滤的结构化值中每个数组元素或对象成员值的 节点;该元素或成员值随后称为当前节点。¶
当前节点可以作为过滤器表达式子表达式中一个或多个 JSONPath
查询的起点,
通过当前节点标识符 @ 记写。
每个 JSONPath 查询既可用于测试查询结果
是否存在,也可用于获取该查询产生的特定 JSON 值,
该值随后可用于比较,或作为
函数实参。¶
过滤器选择器可以使用函数扩展,见 第 2.4 节。 在过滤器选择器的逻辑表达式中,函数 表达式可用于操作节点列表和值。 可用函数集合是可扩展的,其中预定义了若干 函数(见第 2.4 节), 并且“函数扩展”子注册表(第 3.2 节)提供了注册更多 函数的能力。 定义函数时,会为其赋予唯一名称,并为其返回值及每个 形参赋予 声明类型。 类型系统的范围有限;其目的是表达 在没有函数时隐含于 过滤器表达式语法中的限制。 该类型系统还指导转换(第 2.4.2 节),这些转换模拟 未使用函数表达式时语法处理不同类型表达式的 方式。¶
过滤器选择器的形式为
?<logical-expr>。¶
filter-selector = "?" S logical-expr¶
由于过滤器表达式由无副作用的组成部分
构成,
求值顺序不需要(也未)定义。
类似地,对于合取(&&)和析取
(||)(稍后定义),
短路求值实现和完全求值
实现都会得到相同结果;因此这两种实现
策略都是有效的。¶
当前节点可通过当前节点
标识符 @ 访问。
该标识符指向直接包围它的 filter-selector 的当前节点。
注:在嵌套的
filter-selector 中,没有语法可寻址除直接包围它的
filter-selector 之外的任何当前节点(即无法寻址
包围直接
包围该标识符的 filter-selector 的那些 filter-selector 的当前节点)。¶
逻辑表达式提供通常的布尔运算符
(|| 表示 OR,
&& 表示 AND,! 表示 NOT)。
它们具有布尔代数的通常语义,并遵守其定律
(例如,见 [BOOLEAN-LAWS])。
括号MAY在
logical-expr 中用于分组。¶
不要求 logical-expr 由
带括号的表达式组成(这在 [JSONPath-orig] 中曾是要求),
尽管它可以这样写,并且语义
与不带括号时相同。¶
logical-expr = logical-or-expr
logical-or-expr = logical-and-expr *(S "||" S logical-and-expr)
; disjunction
; binds less tightly than conjunction
logical-and-expr = basic-expr *(S "&&" S basic-expr)
; conjunction
; binds more tightly than disjunction
basic-expr = paren-expr /
comparison-expr /
test-expr
paren-expr = [logical-not-op S] "(" S logical-expr S ")"
; parenthesized expression
logical-not-op = "!" ; logical NOT operator
¶
测试表达式
要么测试由嵌入查询指定的节点
是否存在(见第 2.3.5.2.1 节),要么测试
函数表达式的结果(见第 2.4 节)。
在后一种情况下,如果函数的声明结果类型为
LogicalType(见第 2.4.1
节),则测试结果
是否为 LogicalTrue;如果函数的声明结果类型为
NodesType,则测试结果是否非空。
如果函数的声明结果类型为 ValueType,则其在
测试表达式中的使用不是良类型的(见第 2.4.3
节)。¶
test-expr = [logical-not-op S]
(filter-query / ; existence/non-existence
function-expr) ; LogicalType or NodesType
filter-query = rel-query / jsonpath-query
rel-query = current-node-identifier segments
current-node-identifier = "@"
¶
比较表达式可用于比较
基本
值(即数字、字符串、true、false 和
null)。
这些值可以通过字面量值获得;也可以通过单一查询获得,其中每个
单一查询至多选择一个节点,随后使用该节点的值;还可以通过
ValueType 类型的函数表达式(见第
2.4 节)获得。¶
comparison-expr = comparable S comparison-op S comparable
literal = number / string-literal /
true / false / null
comparable = literal /
singular-query / ; singular query value
function-expr ; ValueType
comparison-op = "==" / "!=" /
"<=" / ">=" /
"<" / ">"
singular-query = rel-singular-query / abs-singular-query
rel-singular-query = current-node-identifier singular-query-segments
abs-singular-query = root-identifier singular-query-segments
singular-query-segments = *(S (name-segment / index-segment))
name-segment = ("[" name-selector "]") /
("." member-name-shorthand)
index-segment = "[" index-selector "]"
¶
字面量可以按 JSON 中通常的方式记写 (扩展之处在于 字符串可以使用单引号分隔符)。¶
注:带引号字符串中的字母字符在 ABNF 中不区分大小写,因此在 浮点数中,ABNF 表达式“e”既可以是字符 “e”,也可以是“E”。¶
true、false 和
null 只能为小写(区分大小写)。¶
number = (int / "-0") [ frac ] [ exp ] ; decimal number frac = "." 1*DIGIT ; decimal fraction exp = "e" [ "-" / "+" ] 1*DIGIT ; decimal exponent true = %x74.72.75.65 ; true false = %x66.61.6c.73.65 ; false null = %x6e.75.6c.6c ; null¶
表 10 按优先级从最高 (结合最紧)到最低(结合最松)的顺序列出了过滤器表达式运算符。¶
| 优先级 | 运算符类型 | 语法 |
|---|---|---|
| 5 | 分组 函数表达式 |
(...) name (...)
|
| 4 | 逻辑 NOT |
!
|
| 3 | 关系 |
== !=<
<= > >=
|
| 2 | 逻辑 AND |
&&
|
| 1 | 逻辑 OR |
||
|
过滤器选择器专用于数组和对象。 其结果分别是它们的数组元素或成员值中的(零个、一个、 多个或全部)列表。 应用于基本值时,它不选择任何内容(因此不会 对过滤器选择器的结果作出贡献)。¶
在所得节点列表中,数组的子节点 按其在数组中的位置排序。 对象(相对于数组而言)的子节点 在所得节点列表中出现的顺序未作规定, 因为 JSON 对象是无序的。¶
查询本身位于逻辑上下文中时,就是 存在性测试:如果查询选择至少一个节点,则产生 true; 如果查询不选择任何节点,则产生 false。¶
存在性测试与比较不同之处在于:¶
若要检查查询所选择节点的值,
必须使用显式比较。
例如,若要测试查询
@.foo 所选择的节点是否具有值 null,请使用
@.foo == null(见第 2.6 节),
而不是取反的存在性测试 !@.foo(如果
@.foo 选择了一个节点,无论该节点的
值为何,它都会产生 false)。
类似地,@.foo == false 只有在
@.foo 选择了一个节点且
该节点的值为 false 时才会产生 true。¶
第一组示例展示了一些比较 表达式及其在给定 JSON 值作为输入时的 结果。¶
JSON:¶
{
"obj": {"x": "y"},
"arr": [2, 3]
}
¶
比较:¶
| 比较 | 结果 | 注释 |
|---|---|---|
$.absent1 == $.absent2
|
true | 空节点列表 |
$.absent1 <= $.absent2
|
true |
== 蕴含 <=
|
$.absent == 'g'
|
false | 空节点列表 |
$.absent1 != $.absent2
|
false | 空节点列表 |
$.absent != 'g'
|
true | 空节点列表 |
1 <= 2
|
true | 数值比较 |
1 > 2
|
false | 数值比较 |
13 == '13'
|
false | 类型不匹配 |
'a' <= 'b'
|
true | 字符串比较 |
'a' > 'b'
|
false | 字符串比较 |
$.obj == $.arr
|
false | 类型不匹配 |
$.obj != $.arr
|
true | 类型不匹配 |
$.obj == $.obj
|
true | 对象比较 |
$.obj != $.obj
|
false | 对象比较 |
$.arr == $.arr
|
true | 数组比较 |
$.arr != $.arr
|
false | 数组比较 |
$.obj == 17
|
false | 类型不匹配 |
$.obj != 17
|
true | 类型不匹配 |
$.obj <= $.arr
|
false | 对象和数组
不提供 < 比较 |
$.obj < $.arr
|
false | 对象和数组
不提供 < 比较 |
$.obj <= $.obj
|
true |
== 蕴含 <=
|
$.arr <= $.arr
|
true |
== 蕴含 <=
|
1 <= $.arr
|
false | 数组不提供
< 比较
|
1 >= $.arr
|
false | 数组不提供
< 比较
|
1 > $.arr
|
false | 数组不提供
< 比较
|
1 < $.arr
|
false | 数组不提供
< 比较
|
true <= true
|
true |
== 蕴含 <=
|
true > true
|
false | 布尔值不
提供 < 比较 |
第二组示例展示了一些使用 过滤器选择器的完整 JSONPath 查询,以及这些查询在给定 JSON 值作为输入时的求值结果。 (注:其中两个查询使用了函数扩展;关于 这些内容,请参见第 2.4.6 节和第 2.4.7 节。)¶
JSON:¶
{
"a": [3, 5, 1, 2, 4, 6,
{"b": "j"},
{"b": "k"},
{"b": {}},
{"b": "kilo"}
],
"o": {"p": 1, "q": 2, "r": 3, "s": 5, "t": {"u": 6}},
"e": "f"
}
¶
查询:¶
| 查询 | 结果 | 结果路径 | 注释 |
|---|---|---|---|
$.a[?@.b == 'kilo']
|
{"b": "kilo"}
|
$['a'][9]
|
成员值 比较 |
$.a[?(@.b == 'kilo')]
|
{"b": "kilo"}
|
$['a'][9]
|
带 外围括号的等价查询 |
$.a[?@>3.5]
|
5 4 6
|
$['a'][1] $['a'][4] $['a'][5]
|
数组值比较 |
$.a[?@.b]
|
{"b": "j"} {"b": "k"} {"b": {}} {"b": "kilo"}
|
$['a'][6] $['a'][7] $['a'][8] $['a'][9]
|
数组值存在性 |
$[?@.*]
|
[3, 5, 1, 2, 4, 6, {"b": "j"}, {"b": "k"}, {"b": {}}, {"b": "kilo"}]
{"p": 1, "q": 2, "r": 3, "s": 5, "t": {"u": 6}}
|
$['a'] $['o']
|
非单一查询的 存在性 |
$[?@[?@.b]]
|
[3, 5, 1, 2, 4, 6, {"b": "j"}, {"b": "k"}, {"b": {}}, {"b": "kilo"}]
|
$['a']
|
嵌套过滤器 |
$.o[?@<3, ?@<3]
|
1 2 2 1
|
$['o']['p'] $['o']['q'] $['o']['q'] $['o']['p']
|
非确定性 排序 |
$.a[?@<2 || @.b == "k"]
|
1 {"b": "k"}
|
$['a'][2] $['a'][7]
|
数组值逻辑 OR |
$.a[?match(@.b, "[jk]")]
|
{"b": "j"} {"b": "k"}
|
$['a'][6] $['a'][7]
|
数组值正则 表达式匹配 |
$.a[?search(@.b, "[jk]")]
|
{"b": "j"} {"b": "k"} {"b": "kilo"}
|
$['a'][6] $['a'][7] $['a'][9]
|
数组值正则 表达式搜索 |
$.o[?@>1 && @<4]
|
2 3
|
$['o']['q'] $['o']['r']
|
对象值逻辑 AND |
$.o[?@>1 && @<4]
|
3 2
|
$['o']['r'] $['o']['q']
|
另一种结果 |
$.o[?@.u || @.x]
|
{"u": 6}
|
$['o']['t']
|
对象值逻辑 OR |
$.a[?@.b == $.x]
|
3 5 1 2 4 6
|
$['a'][0] $['a'][1] $['a'][2] $['a'][3] $['a'][4] $['a'][5]
|
无值查询的 比较 |
$.a[?@ == @]
|
3 5 1 2 4 6 {"b": "j"} {"b": "k"} {"b": {}} {"b": "kilo"}
|
$['a'][0] $['a'][1]
$['a'][2] $['a'][3]
$['a'][4] $['a'][5]
$['a'][6] $['a'][7]
$['a'][8] $['a'][9]
|
基本值和 结构化值的比较 |
上面使用查询
$.o[?@<3, ?@<3] 的示例表明,过滤器选择器每次出现在子段中时,
可能产生顺序不同的节点列表。¶
除前面小节中定义的过滤器表达式功能之外, JSONPath 还定义了一个可用于 添加过滤器表达式功能的扩展点:“函数扩展”。¶
本节定义该扩展点,以及一些使用该扩展点的 函数扩展。 虽然这些机制被设计为使用该扩展点, 但它们是 JSONPath 规范的组成部分,并且 预期像本规范任何其他组成部分一样被 实现。¶
函数扩展定义一个已注册名称(见第 3.2 节),该名称 可应用于由零个或多个实参组成的序列,并产生一个 结果。每个已注册函数名都是唯一的。¶
函数扩展MUST被定义为其 求值没有副作用,即包含它的表达式的所有可能求值顺序以及 短路求值或完全求值选择 MUST都会得到相同结果。 (注:在此意义上,记忆化或日志记录不是副作用, 因为它们只在实现层级可见——它们不会 影响求值结果。)¶
function-name = function-name-first *function-name-char
function-name-first = LCALPHA
function-name-char = function-name-first / "_" / DIGIT
LCALPHA = %x61-7A ; "a".."z"
function-expr = function-name "(" S [function-argument
*(S "," S function-argument)] S ")"
function-argument = literal /
filter-query / ; (includes singular-query)
logical-expr /
function-expr
¶
查询中的任何函数表达式都必须是格式良好的(符合上述 ABNF) 且是良类型的; 否则,JSONPath 实现MUST引发错误 (见第 2.1 节)。 为定义哪些函数表达式是良类型的, 首先引入一个类型系统。¶
函数扩展的每个形参和结果都必须具有 声明类型。¶
声明类型使得可以独立于 JSONPath 查询所应用到的任何查询实参, 检查 JSONPath 查询的良类型性。¶
| 类型 | 实例 |
|---|---|
ValueType
|
JSON 值或
Nothing
|
LogicalType
|
LogicalTrue 或 LogicalFalse
|
NodesType
|
节点列表 |
注:¶
正如查询可通过测试至少一个节点
是否存在(第
2.3.5.2.1 节)而用于逻辑表达式一样,
声明类型为 NodesType 的函数表达式也可作为
声明类型为 LogicalType 的形参的函数实参,并采用等价的转换
规则:¶
注:¶
NodesType 到
ValueType 的隐式转换
可能令人意外,因此未定义这种转换。¶
NodesType 的函数表达式,可以
通过把该表达式包装到某个函数扩展调用中,
间接用作声明类型为
ValueType 的形参的实参,例如 value()(见第 2.4.8 节),
该函数接受 NodesType 类型的形参并返回
ValueType 类型的结果。¶
现在可以依据此类型系统定义函数表达式的 良类型性。¶
对于函数表达式而言,要成为良类型:¶
length() 函数扩展
length() 函数扩展提供了一种
计算值的长度
并使其可供过滤器表达式中进一步处理的方式:¶
$[?length(@.authors) >= 5]¶
它的唯一实参是 ValueType 的实例(可能
来自
单一查询,如上例所示)。结果也是
ValueType 的实例:无符号整数或特殊结果
Nothing。¶
count() 函数扩展
count() 函数扩展提供了一种获取
节点列表中节点数量
并使其可供过滤器表达式中进一步处理的方式:¶
$[?count(@.*.author) >= 5]¶
它的唯一实参是节点列表。 结果是一个值(无符号整数),给出 节点列表中的节点数量。¶
注:¶
match() 函数扩展
match() 函数扩展提供了一种检查
给定字符串是否(整体;见第 2.4.7 节)匹配给定
正则表达式的方式,该正则表达式采用 [RFC9485]
中描述的形式。¶
$[?match(@.date, "1974-05-..")]¶
其实参是 ValueType 的实例(可能
来自
单一查询,如上例中的第一个实参)。
如果第一个实参不是字符串,或第二个实参不是
符合 [RFC9485] 的字符串,
则结果为 LogicalFalse。
否则,第一个实参所对应的字符串会与
第二个实参所对应字符串中包含的 I-Regexp 进行匹配;
如果该字符串匹配该 I-Regexp,则结果为 LogicalTrue,否则为
LogicalFalse。¶
search() 函数扩展
search() 函数扩展提供了一种检查
给定字符串是否包含与给定
正则表达式匹配的子字符串的方式,该正则表达式采用 [RFC9485]
中描述的形式。¶
$[?search(@.author, "[BR]ob")]¶
其实参是 ValueType 的实例(可能
来自
单一查询,如上例中的第一个实参)。
如果第一个实参不是字符串,或第二个实参不是
符合 [RFC9485] 的字符串,
则结果为 LogicalFalse。
否则,会在第一个实参所对应的字符串中搜索
与第二个实参所对应字符串中包含的 I-Regexp
匹配的子字符串;如果至少存在一个这样的子字符串,则结果为
LogicalTrue,否则为 LogicalFalse。¶
value() 函数扩展
value() 函数扩展提供了一种将
NodesType 的实例转换为值,并
使其可供过滤器表达式中进一步处理的方式:¶
$[?value(@..color) == "red"]¶
它的唯一实参是 NodesType 的实例(可能
来自
filter-query,如上例所示)。结果是
ValueType 的实例。¶
注:单一查询可用于任何预期 ValueType 的位置,
因此无需对单一查询使用 value() 函数扩展。¶
| 查询 | 注释 |
|---|---|
$[?length(@) < 3]
|
良类型 |
$[?length(@.*) < 3]
|
非良类型,因为
@.* 是非单一查询
|
$[?count(@.*) == 1]
|
良类型 |
$[?count(1) == 1]
|
非良类型,因为
1 不是查询或函数表达式
|
$[?count(foo(@.*)) == 1]
|
良类型,其中
foo() 是具有 NodesType 类型形参
且结果类型为 NodesType 的函数扩展
|
$[?match(@.timezone, 'Europe/.*')]
|
良类型 |
$[?match(@.timezone, 'Europe/.*') == true]
|
非良类型,因为
LogicalType 不能用于比较
|
$[?value(@..color) == "red"]
|
良类型 |
$[?value(@..color)]
|
非良类型,因为
ValueType 不能用于测试表达式
|
$[?bar(@.a)]
|
对于任何形参具有任意声明类型且结果类型为
LogicalType 的函数
bar(),都是良类型
|
$[?bnl(@.*)]
|
对于任何形参声明类型为
NodesType 或 LogicalType 且结果类型为
LogicalType 的函数
bnl(),都是良类型
|
$[?blt(1==1)]
|
良类型,其中
blt() 是形参声明类型为
LogicalType 且结果类型为 LogicalType 的函数
|
$[?blt(1)]
|
对于同一个
函数 blt(),非良类型,因为 1 不是查询、
logical-expr 或函数表达式
|
$[?bal(1)]
|
良类型,其中
bal() 是形参声明类型为
ValueType 且结果类型为 LogicalType 的函数
|
对于输入节点列表中的每个节点, 段会将一个或多个选择器应用于该节点,并把 每个选择器的结果连接为按输入节点划分的节点列表,然后再 按输入节点列表的顺序将这些节点列表连接起来,形成单个 段结果节点列表。¶
结果表明,查询中的段越多, 所得节点列表中节点在输入值中的 深度就越大:¶
段有两种:子段和后代段。¶
segment = child-segment / descendant-segment¶
每种段的语法和语义定义如下。¶
子段由一个非空的、逗号分隔的 选择器序列组成,该序列括在方括号中。¶
当只有单个 通配符选择器或名称选择器时,也提供了简写记法。¶
child-segment = bracketed-selection /
("."
(wildcard-selector /
member-name-shorthand))
bracketed-selection = "[" S selector *(S "," S selector) S "]"
member-name-shorthand = name-first *name-char
name-first = ALPHA /
"_" /
%x80-D7FF /
; skip surrogate code points
%xE000-10FFFF
name-char = name-first / DIGIT
DIGIT = %x30-39 ; 0-9
ALPHA = %x41-5A / %x61-7A ; A-Z / a-z
¶
.* 是一个直接由
wildcard-selector 构建的 child-segment,
是 [*] 的简写。¶
.<member-name> 是一个由
member-name-shorthand 构建的
child-segment,是
['<member-name>'] 的简写。
注:这只能用于由某些
字符组成的成员名,如 ABNF 规则 member-name-shorthand 所指定。
因此,例如,$.foo.bar 是
$['foo']['bar'] 的简写(但不是 $['foo.bar'] 的简写)。¶
后代段由双点号
..
后跟一个子段(使用方括号记法)组成。¶
也提供了与子段简写形式相对应的 简写记法。¶
descendant-segment = ".." (bracketed-selection /
wildcard-selector /
member-name-shorthand)
¶
..* 是直接由
wildcard-selector 构建的 descendant-segment,
是 ..[*] 的简写。¶
..<member-name> 是由
member-name-shorthand 构建的
descendant-segment,
是 ..['<member-name>'] 的简写。
注:与 child-segment 的类似简写一样,这
只能用于由某些
字符组成的成员名,如 ABNF 规则 member-name-shorthand 所指定。¶
注:单独的 .. 不是有效的
段。¶
后代段会产生输入值的零个或多个后代。¶
对于输入节点列表中的每个节点, 后代选择器会访问该输入节点及其每一个 后代,并满足:¶
对象的子节点被访问的顺序 未作规定,因为 JSON 对象是无序的。¶
假设后代段的形式为
..[<selectors>](在将任何简写
形式转换为方括号记法之后),
并且按访问顺序排列的节点为 D1、...、Dn
(其中 n >= 1)。
注:D1 是输入值。¶
对于每个满足
1 <= i <= n 的 i,节点列表 Ri 被定义为
将子段 [<selectors>] 应用于节点
Di 的结果。¶
对于输入节点列表中的每个节点,
后代段的结果是 R1、
...、Rn(按该顺序)的连接。
然后这些结果再按输入节点列表顺序连接,形成
该段的结果。¶
总之,后代段会向每个输入值的结构中 向下深入一级或多级。¶
JSON:¶
{
"o": {"j": 1, "k": 2},
"a": [5, 3, [{"j": 4}, {"k": 6}]]
}
¶
查询:¶
(注意,第四个示例可以用两个 等价 查询表示,它们在表 16中显示在同一表行中,而不是两个 几乎相同的行中。)¶
| 查询 | 结果 | 结果路径 | 注释 |
|---|---|---|---|
$..j
|
1 4
|
$['o']['j'] $['a'][2][0]['j']
|
对象值 |
$..j
|
4 1
|
$['a'][2][0]['j'] $['o']['j']
|
另一种结果 |
$..[0]
|
5 {"j": 4}
|
$['a'][0] $['a'][2][0]
|
数组值 |
$..[*] 或 $..*
|
{"j": 1, "k": 2} [5, 3, [{"j": 4}, {"k": 6}]] 1
2 5 3 [{"j": 4}, {"k": 6}] {"j": 4}
{"k": 6} 4 6
|
$['o'] $['a'] $['o']['j'] $['o']['k'] $['a'][0] $['a'][1] $['a'][2] $['a'][2][0] $['a'][2][1] $['a'][2][0]['j']
$['a'][2][1]['k']
|
所有值 |
$..o
|
{"j": 1, "k": 2}
|
$['o']
|
输入值被访问 |
$.o..[*, *]
|
1 2 2 1
|
$['o']['j'] $['o']['k'] $['o']['k'] $['o']['j']
|
非确定性 排序 |
$.a..[0, 1]
|
5 3 {"j": 4}
{"k": 6}
|
$['a'][0] $['a'][1] $['a'][2][0] $['a'][2][1]
|
多个段 |
注:上面 $..[*] 和 $..* 示例的
结果排序并不保证,
但以下情况除外:¶
{"j": 1, "k": 2} 必须出现在 1 和
2 之前,¶
[5, 3, [{"j": 4}, {"k": 6}]] 必须出现在 5、
3 和 [{"j": 4}, {"k": 6}] 之前,¶
5 必须出现在 3 之前,而 3 必须出现在
[{"j": 4}, {"k": 6}] 之前,¶
5 和 3 必须出现在 {"j": 4}、
4、{"k": 6} 和 6 之前,¶
[{"j": 4}, {"k": 6}] 必须出现在 {"j": 4}
和 {"k": 6} 之前,¶
{"j": 4} 必须出现在 {"k": 6} 之前,¶
{"k": 6} 必须出现在 4 之前,并且¶
4 必须出现在 6 之前。¶
上面使用查询 $.o..[*, *] 的示例
表明,选择器每次出现在后代段中时,都可能产生顺序不同的节点列表。¶
上面使用查询 $.a..[0, 1] 的示例
表明,子段 [0, 1] 会依次应用于每个节点
(而不是每个选择器访问节点一次;一些不符合本规范的
JSONPath 实现就是这种情况)。¶
null 的语义
注:JSON null 与任何其他 JSON 值一样处理,
即它不被视为表示“未定义”或“缺失”。¶
JSON:¶
{"a": null, "b": [null], "c": [{}], "null": 1}
¶
查询:¶
| 查询 | 结果 | 结果路径 | 注释 |
|---|---|---|---|
$.a
|
null
|
$['a']
|
对象值 |
$.a[0]
|
null 被当作数组使用
|
||
$.a.d
|
null 被当作对象使用
|
||
$.b[0]
|
null
|
$['b'][0]
|
数组值 |
$.b[*]
|
null
|
$['b'][0]
|
数组值 |
$.b[?@]
|
null
|
$['b'][0]
|
存在性 |
$.b[?@==null]
|
null
|
$['b'][0]
|
比较 |
$.c[?@.d==null]
|
与“缺失” 值进行比较 | ||
$.null
|
1
|
$['null']
|
根本不是 JSON null,
只是成员名字符串 |
规范化路径是值中节点位置的唯一表示,
可在该值中唯一标识该节点。
具体而言,规范化路径是一种具有受限语法(定义如下)的 JSONPath 查询,
例如 $['book'][3],当应用于该值时,会得到一个
仅由该规范化路径所标识节点组成的节点列表。
注:规范化路径表示特定值中某个节点的身份。
在一个值中,标识任何特定节点的规范化路径恰好只有一个。¶
节点列表可以在 JSON 中紧凑地表示为字符串数组,其中 这些字符串是 规范化路径。¶
规范化路径提供了一种可预测的格式,可简化节点列表的测试和 后处理, 例如用于移除重复节点。 在本文档中,规范化路径用作示例中的结果路径。¶
规范化路径使用规范的方括号记法,而不是点 记法。¶
规范化路径使用单引号来界定字符串成员名。 这会减少 当规范化路径出现在用双引号界定的字符串中(例如 JSON 文本中)时 需要转义的字符数量。¶
某些字符在规范化路径中以唯一一种方式转义; 所有其他 字符均不转义。¶
normalized-path = root-identifier *(normal-index-segment)
normal-index-segment = "[" normal-selector "]"
normal-selector = normal-name-selector / normal-index-selector
normal-name-selector = %x27 *normal-single-quoted %x27 ; 'string'
normal-single-quoted = normal-unescaped /
ESC normal-escapable
normal-unescaped = ; omit %x0-1F control codes
%x20-26 /
; omit 0x27 '
%x28-5B /
; omit 0x5C \
%x5D-D7FF /
; skip surrogate code points
%xE000-10FFFF
normal-escapable = %x62 / ; b BS backspace U+0008
%x66 / ; f FF form feed U+000C
%x6E / ; n LF line feed U+000A
%x72 / ; r CR carriage return U+000D
%x74 / ; t HT horizontal tab U+0009
"'" / ; ' apostrophe U+0027
"\" / ; \ backslash (reverse solidus) U+005C
(%x75 normal-hexchar)
; certain values u00xx U+00XX
normal-hexchar = "0" "0"
(
("0" %x30-37) / ; "00"-"07"
; omit U+0008-U+000A BS HT LF
("0" %x62) / ; "0b"
; omit U+000C-U+000D FF CR
("0" %x65-66) / ; "0e"-"0f"
("1" normal-HEXDIG)
)
normal-HEXDIG = DIGIT / %x61-66 ; "0"-"9", "a"-"f"
normal-index-selector = "0" / (DIGIT1 *DIGIT)
; non-negative decimal integer
¶
由于标识给定节点的规范化路径只能有一个,
该语法
规定了哪些字符要转义、哪些字符不转义。
因此,normal-hexchar 的定义被设计用于对那些
不容易直接打印的字符进行十六进制转义,例如 U+000B LINE TABULATION,
但这些字符没有可用的标准 JSON 转义(例如 \n)。¶
根据本规范,IANA 已在 新的“JSONPath”注册表中创建新的“Function Extensions” 子注册表。“Function Extensions”子注册表采用“专家审查”策略 ([RFC8126] 的 第 4.5 节 )。¶
专家被指示要谨慎分配那些暗示一般适用语义的函数 扩展名称, 将它们保留给可能被广泛 使用且能充分利用其简洁性的函数。 专家还被指示要求注册人提供一份 规范([RFC8126] 的 第 4.6 节),但可以作出例外, 例如,当注册时尚无可用规范但很可能即将 提供时。 如果专家了解到已有函数扩展被部署并 使用,若其认为此类注册可避免潜在的未来冲突, 也可以自行发起注册。¶
子注册表中的每个条目都必须包含以下内容:¶
一个小写 ASCII [RFC0020] 字符串,以字母开头,之后可以
包含字母、数字和下划线字符
([a-z][_a-z0-9]*)。子注册表中的其他条目不能具有
相同的函数名。¶
简要描述¶
零个或多个声明类型的逗号分隔列表,每个类型对应 此函数扩展所预期的一个 实参¶
此函数扩展结果的声明类型¶
提供该函数扩展描述的参考文档¶
此子注册表的初始条目列于表 19;“Change Controller”列中的 条目均具有值“IETF”, 而“Reference”列中的条目 均具有值“RFC 9535 的第 2.4 节”:¶
| 函数名称 | 简要描述 | 形参 | 结果 |
|---|---|---|---|
| length | 字符串、数组或对象的长度 |
ValueType
|
ValueType
|
| count | 节点列表大小 |
NodesType
|
ValueType
|
| match | 正则表达式完整匹配 |
ValueType, ValueType
|
LogicalType
|
| search | 正则表达式子字符串匹配 |
ValueType, ValueType
|
LogicalType
|
| value | 节点列表中单个节点的值 |
NodesType
|
ValueType
|
JSONPath 的安全注意事项可源自:¶
历史上,JSONPath 常常通过将查询的部分内容传递给
底层编程语言引擎来实现,例如
JavaScript 的 eval() 函数。
众所周知,这种方法会导致注入攻击,并且
需要完美的输入验证才能防止这些攻击(关于
JSON 本身的类似考虑,见
[RFC8259] 的 第 12 节)。
相反,JSONPath 实现需要实现查询的完整语法,
而不依赖编程语言
引擎的解析器。¶
针对可用性的攻击可能试图触发某些实现 在某些情况下表现出的异常昂贵的运行时性能。 (关于哈希表实现中的问题,见 [RFC8949] 的 第 10 节, 关于正则表达式实现中的性能问题,见 [RFC9485] 的 第 8 节。) 实现者需要意识到,只要攻击者能够选择提交特制的 JSONPath 查询或查询实参,从而触发出人意料地高、甚至可能呈 指数级的 CPU 使用率,或者例如通过对后代段的朴素递归实现导致 栈溢出,那么良好的平均性能就 不足够。实现需要具有适当的资源管理 来缓解这些攻击。¶
JSONPath 查询通常不是静态的,而是由提供索引值、 成员名或过滤器表达式中用于比较的值的变量 形成。 这些变量需要经过验证(例如,仅在给定值允许时才允许形成 .name 之类的特定构造)和转换 (例如,通过转义字符串分隔符)。 未正确执行这些验证和转换,可能导致意外 失败,进而可能导致可用性、机密性和 完整性破坏,尤其是在对手能够控制这些 值时(例如,通过把它们输入到 Web 表单中)。 由此产生的攻击类别,即注入(例如 SQL 注入), 一直位列应用安全 漏洞的主要原因之中,需要特别关注。¶
本附录汇总了贯穿本文档所用 ABNF 段落中的 ABNF 语法。¶
图 2 包含定义 JSONPath 查询语法的汇总 ABNF 语法。¶
jsonpath-query = root-identifier segments
segments = *(S segment)
B = %x20 / ; Space
%x09 / ; Horizontal tab
%x0A / ; Line feed or New line
%x0D ; Carriage return
S = *B ; optional blank space
root-identifier = "$"
selector = name-selector /
wildcard-selector /
slice-selector /
index-selector /
filter-selector
name-selector = string-literal
string-literal = %x22 *double-quoted %x22 / ; "string"
%x27 *single-quoted %x27 ; 'string'
double-quoted = unescaped /
%x27 / ; '
ESC %x22 / ; \"
ESC escapable
single-quoted = unescaped /
%x22 / ; "
ESC %x27 / ; \'
ESC escapable
ESC = %x5C ; \ backslash
unescaped = %x20-21 / ; see RFC 8259
; omit 0x22 "
%x23-26 /
; omit 0x27 '
%x28-5B /
; omit 0x5C \
%x5D-D7FF /
; skip surrogate code points
%xE000-10FFFF
escapable = %x62 / ; b BS backspace U+0008
%x66 / ; f FF form feed U+000C
%x6E / ; n LF line feed U+000A
%x72 / ; r CR carriage return U+000D
%x74 / ; t HT horizontal tab U+0009
"/" / ; / slash (solidus) U+002F
"\" / ; \ backslash (reverse solidus) U+005C
(%x75 hexchar) ; uXXXX U+XXXX
hexchar = non-surrogate /
(high-surrogate "\" %x75 low-surrogate)
non-surrogate = ((DIGIT / "A"/"B"/"C" / "E"/"F") 3HEXDIG) /
("D" %x30-37 2HEXDIG )
high-surrogate = "D" ("8"/"9"/"A"/"B") 2HEXDIG
low-surrogate = "D" ("C"/"D"/"E"/"F") 2HEXDIG
HEXDIG = DIGIT / "A" / "B" / "C" / "D" / "E" / "F"
wildcard-selector = "*"
index-selector = int ; decimal integer
int = "0" /
(["-"] DIGIT1 *DIGIT) ; - optional
DIGIT1 = %x31-39 ; 1-9 non-zero digit
slice-selector = [start S] ":" S [end S] [":" [S step ]]
start = int ; included in selection
end = int ; not included in selection
step = int ; default: 1
filter-selector = "?" S logical-expr
logical-expr = logical-or-expr
logical-or-expr = logical-and-expr *(S "||" S logical-and-expr)
; disjunction
; binds less tightly than conjunction
logical-and-expr = basic-expr *(S "&&" S basic-expr)
; conjunction
; binds more tightly than disjunction
basic-expr = paren-expr /
comparison-expr /
test-expr
paren-expr = [logical-not-op S] "(" S logical-expr S ")"
; parenthesized expression
logical-not-op = "!" ; logical NOT operator
test-expr = [logical-not-op S]
(filter-query / ; existence/non-existence
function-expr) ; LogicalType or NodesType
filter-query = rel-query / jsonpath-query
rel-query = current-node-identifier segments
current-node-identifier = "@"
comparison-expr = comparable S comparison-op S comparable
literal = number / string-literal /
true / false / null
comparable = literal /
singular-query / ; singular query value
function-expr ; ValueType
comparison-op = "==" / "!=" /
"<=" / ">=" /
"<" / ">"
singular-query = rel-singular-query / abs-singular-query
rel-singular-query = current-node-identifier singular-query-segments
abs-singular-query = root-identifier singular-query-segments
singular-query-segments = *(S (name-segment / index-segment))
name-segment = ("[" name-selector "]") /
("." member-name-shorthand)
index-segment = "[" index-selector "]"
number = (int / "-0") [ frac ] [ exp ] ; decimal number
frac = "." 1*DIGIT ; decimal fraction
exp = "e" [ "-" / "+" ] 1*DIGIT ; decimal exponent
true = %x74.72.75.65 ; true
false = %x66.61.6c.73.65 ; false
null = %x6e.75.6c.6c ; null
function-name = function-name-first *function-name-char
function-name-first = LCALPHA
function-name-char = function-name-first / "_" / DIGIT
LCALPHA = %x61-7A ; "a".."z"
function-expr = function-name "(" S [function-argument
*(S "," S function-argument)] S ")"
function-argument = literal /
filter-query / ; (includes singular-query)
logical-expr /
function-expr
segment = child-segment / descendant-segment
child-segment = bracketed-selection /
("."
(wildcard-selector /
member-name-shorthand))
bracketed-selection = "[" S selector *(S "," S selector) S "]"
member-name-shorthand = name-first *name-char
name-first = ALPHA /
"_" /
%x80-D7FF /
; skip surrogate code points
%xE000-10FFFF
name-char = name-first / DIGIT
DIGIT = %x30-39 ; 0-9
ALPHA = %x41-5A / %x61-7A ; A-Z / a-z
descendant-segment = ".." (bracketed-selection /
wildcard-selector /
member-name-shorthand)
图 3 包含
定义 JSONPath 规范化路径语法的汇总 ABNF 语法,同时还使用
图 2 中的规则
root-identifier、ESC、DIGIT 和 DIGIT1。¶
normalized-path = root-identifier *(normal-index-segment)
normal-index-segment = "[" normal-selector "]"
normal-selector = normal-name-selector / normal-index-selector
normal-name-selector = %x27 *normal-single-quoted %x27 ; 'string'
normal-single-quoted = normal-unescaped /
ESC normal-escapable
normal-unescaped = ; omit %x0-1F control codes
%x20-26 /
; omit 0x27 '
%x28-5B /
; omit 0x5C \
%x5D-D7FF /
; skip surrogate code points
%xE000-10FFFF
normal-escapable = %x62 / ; b BS backspace U+0008
%x66 / ; f FF form feed U+000C
%x6E / ; n LF line feed U+000A
%x72 / ; r CR carriage return U+000D
%x74 / ; t HT horizontal tab U+0009
"'" / ; ' apostrophe U+0027
"\" / ; \ backslash (reverse solidus) U+005C
(%x75 normal-hexchar)
; certain values u00xx U+00XX
normal-hexchar = "0" "0"
(
("0" %x30-37) / ; "00"-"07"
; omit U+0008-U+000A BS HT LF
("0" %x62) / ; "0b"
; omit U+000C-U+000D FF CR
("0" %x65-66) / ; "0e"-"0f"
("1" normal-HEXDIG)
)
normal-HEXDIG = DIGIT / %x61-66 ; "0"-"9", "a"-"f"
normal-index-selector = "0" / (DIGIT1 *DIGIT)
; non-negative decimal integer
本附录为资料性内容。¶
JSONPath 被发明时,XML 因可用的 强大工具而闻名,这些工具可用于分析、转换以及从 XML 文档中选择性提取数据。 [XPath] 就是这些工具之一。¶
到 2007 年,为新兴 JSON 社区解决同类问题的需求变得明显,具体用于:¶
(注:XPath 自 2007 年以来已有发展,较新的版本甚至 名义上支持在 JSON 值内部进行操作。 本附录仅讨论 2007 年可用且使用更广泛的 XPath 版本。)¶
JSONPath 吸收了 XPath 的整体感觉,但将其概念 映射到使用动态语言处理 JSON 的人会熟悉的语法 (以及部分语义)。¶
例如,在 JavaScript、Python 和 PHP 等流行的动态编程语言中, XPath 表达式的语义:¶
/store/book[1]/title¶
可以在以下表达式中实现:¶
x.store.book[0].title¶
或在方括号记法中实现:¶
x['store']['book'][0]['title']¶
其中变量 x 持有查询实参。¶
JSONPath 语言被设计为:¶
JSONPath 表达式作用于 JSON 值的方式,
与 XPath 表达式结合 XML 文档使用的方式相同。
JSONPath 使用 $ 来指代查询实参的根节点,类似于
XPath 中开头的 /。¶
JSONPath 表达式使用点记法
($.store.book[0].title)
或方括号记法
($['store']['book'][0]['title'])进一步向层级下方移动;二者都替代 XPath 查询
表达式中的 /,其中点记法作为轻量但受限的语法,
而方括号记法则是
重量级但更通用的语法。¶
JSONPath 和 XPath 都使用 * 作为通配符。
JSONPath 的后代段记法以 .. 开头,借自 [E4X],类似于 XPath 的 //。
数组切片构造 [start:end:step] 是 JSONPath 独有的,
受 ECMASCRIPT 4 中 [SLICE] 启发。¶
过滤器表达式通过语法
?<logical-expr> 支持,如:¶
$.store.book[?@.price < 10].title¶
表 20 扩展了 表 1,提供与类似 XPath 概念的比较。¶
| XPath | JSONPath | 描述 |
|---|---|---|
/
|
$
|
根 XML 元素 |
.
|
@
|
当前 XML 元素 |
/
|
. 或 []
|
子运算符 |
..
|
不适用 | 父运算符 |
//
|
..name、..[index]、..* 或
..[*]
|
后代(JSONPath 从 E4X 借用了此 语法) |
*
|
*
|
通配符:所有 XML 元素,不论 其名称如何 |
@
|
不适用 | 属性访问:JSON 值 没有属性 |
[]
|
[]
|
下标运算符,用于迭代 XML 元素集合以及用于谓词 |
|
|
[,]
|
联合运算符(结果为 节点集的组合);在 JSONPath 中称为列表运算符,允许组合 成员名、数组索引和切片 |
| 不适用 |
[start:end:step]
|
从 ES4 借用的数组切片运算符 |
[]
|
?
|
应用过滤器(脚本)表达式 |
| 无缝 | 不适用 | 表达式引擎 |
()
|
不适用 | 分组 |
为了进一步说明,表 21 展示了一些 XPath 表达式 及其 JSONPath 等价形式。¶
| XPath | JSONPath | 结果 |
|---|---|---|
/store/book/author
|
$.store.book[*].author
|
store 中所有书的作者 |
//author
|
$..author
|
所有作者 |
/store/*
|
$.store.*
|
store 中的所有东西,即一些 书和一辆红色自行车 |
/store//price
|
$.store..price
|
store 中所有东西的价格 |
//book[3]
|
$..book[2]
|
第三本书 |
//book[last()]
|
$..book[-1]
|
按顺序的最后一本书 |
//book[position()<3]
|
$..book[0,1]$..book[:2]
|
前两本书 |
//book[isbn]
|
$..book[?@.isbn]
|
筛选所有带有 ISBN 编号的书 |
//book[price<10]
|
$..book[?@.price<10]
|
筛选所有价格低于 10 的书 |
//*
|
$..*
|
XML 文档中的所有元素;输入值中包含的所有 成员值和数组元素 |
XPath 拥有比此比较中列出的多得多的功能(未缩写语法中的位置路径、 运算符和函数)。此外,XPath 与 JSONPath 中下标运算符的工作方式存在显著差异:¶
本附录为资料性内容。¶
相对于 JSON Pointer [RFC6901],JSONPath 并不旨在替代它,而是作为一个更强大的 伴随标准。两个标准的目的 不同。¶
JSON Pointer 用于在结构已知的 JSON 值中标识单个值。¶
JSONPath 可以在 JSON 值中标识单个值,例如通过 使用规范化路径。但 JSONPath 也是一种查询语法,可用于 从结构仅以一般方式已知的 JSON 值中搜索并提取 多个值。¶
规范化 JSONPath 可以通过转换语法转换为 JSON Pointer, 而无需了解任何 JSON 值。反向转换通常并不成立,即 JSON Pointer 中的数字 引用标记(路径组件)可能标识对象的成员值,也可能标识数组的 元素。 为了转换为 JSONPath 查询,需要了解 JSON 值的结构 以区分这些情况。¶
本文档基于 Stefan Gössner 最初定义 JSONPath 的在线文章 [JSONPath-orig]。¶
书籍示例取自德国比勒费尔德大学 2002 年使用的课程材料。¶
本工作感谢 Christoph Burgmer 出色的 JSONPath 比较项目 [COMPARISON],该项目 详细说明了四十多个 JSONPath 实现应用于众多查询时的行为。¶