RFC 9535 JSONPath 2024 年 2 月
Gössner 等 标准路线 [页]
文档流:
互联网工程任务组(IETF)
RFC:
9535
类别:
标准路线
发布:
ISSN:
2070-1721
作者:
S. Gössner,编。
多特蒙德应用科学大学
G. Normington,编。
C. Bormann,编。
不来梅大学 TZI

RFC 9535

JSONPath:JSON 查询表达式

摘要

JSONPath 定义了一种字符串语法,用于从给定的 JSON 值中选择并提取 JSON(RFC 8259)值 。

本文档的状态

这是一份互联网标准路线文档。

本文档是互联网工程任务组 (IETF)的产物。它代表 IETF 社区的共识。它已经 过公开审查,并已获互联网工程指导组(IESG)批准发布。 关于互联网标准的更多信息可见 RFC 7841 第 2 节。

关于本文档当前状态、任何 勘误以及如何提供反馈的信息,可在 https://www.rfc-editor.org/info/rfc9535 获取。

目录

1. 引言

JSON [RFC8259] 是一种流行的 结构化数据值 表示格式。 JSONPath 定义了一种字符串语法,用于从给定的 JSON 值中 选择并提取 JSON 值。

相对于 JSON Pointer [RFC6901],JSONPath 并非旨在作为替代品,而是作为一个 更强大的 配套工具。见附录 C

1.1. 术语

本文档中的关键词“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”并不具有 通用含义,例如它们在一般编程语境中可能具有的含义。

采用 [RFC9485] 的术语。

本文档中使用的其他术语定义如下。

值:

按照 [RFC8259],符合 JSON 通用数据模型的数据项,即 基本数据(数字、文本字符串以及特殊 值 null、true 和 false),或结构化数据(JSON 对象和数组)。 [RFC8259] 侧重于 JSON 值的文本 表示,并且 没有完整定义此处假定的值抽象。

成员:

对象中的名称/值对。(成员本身不是 值。)

名称:

构成成员的名称/值对中的名称(一个字符串)。 这也在 [RFC8259] 中使用, 但该规范并未 正式定义它。 此处包含该术语是为了完整性。

元素:

JSON 数组中的一个值。

索引:

标识数组中特定元素的整数。

查询:

JSONPath 表达式的简称。

查询实参:

JSONPath 表达式所应用到的值的简称。

位置:

值在查询实参中的位置。可以将其 看作一系列名称和索引,通过 查询实参中的对象和数组导航到该值,空序列 表示查询实参本身。 位置可以表示为规范化路径(定义如下)。

节点:

一个值及其在查询实参中的位置所组成的对。

根节点:

其值为整个查询实参的唯一节点。

根节点标识符:

表达式 $,它引用查询实参的根节点。

当前节点标识符:

表达式 @,它在过滤器表达式求值的 语境中引用当前节点(稍后描述)。

子节点(某节点的):

如果该节点是数组,则为其元素的节点;如果该节点 是对象,则为其成员值的节点。 如果该节点既不是数组也不是对象,则它没有子节点。

后代(某节点的):

该节点的子节点,以及其 子节点的子节点,如此递归。 更形式化地说,节点之间的“后代”关系是“子节点” 关系的传递闭包。

深度(值中后代节点的):

该节点在该值中的祖先数量。该值的根 节点深度为零, 根节点的子节点深度为一,它们的子节点深度为二,依此类推。

节点列表:

节点的列表。 虽然节点列表可以用 JSON 表示,例如表示为数组,但本文档 不要求也不假定任何特定表示。

形参:

形式参数(函数的),可在函数表达式中 接受函数实参 (实际参数)。

规范化路径:

JSONPath 表达式的一种形式,它通过 提供一个查询来标识值中的某个节点,该查询的结果正好是该节点。 查询实参中的每个节点都由恰好一个规范化路径标识 (我们说该规范化路径对该节点是“唯一”的),并且要成为特定查询实参的 规范化路径,该规范化路径需要标识 恰好一个节点。这类似于 JSON Pointer [RFC6901],但语法上不同。 注:此定义基于第 2.7 节中的语法定义; 能够标识值中某个节点但不符合该语法的 JSONPath 表达式不是规范化路径。

Unicode 标量值:

除高代理和 低代理码点之外的任何 Unicode [UNICODE] 码点(换言之,十六进制闭区间 0 到 D7FF 或 E000 到 10FFFF 中的整数)。JSONPath 查询是 Unicode 标量值序列。

段:

用于选择输入值的子节点 ([<selectors>]) 或后代(..⁠[<selectors>])的构造之一。

选择器:

段内的单个项,它接受输入值并 产生一个由输入值的子节点组成的节点列表。

单一查询:

由在语法上以某种方式 受限的段构建的 JSONPath 表达式(第 2.3.5.1 节),从而无论输入 值为何,该表达式都会产生包含至多一个节点的节点列表。 注:总是产生单一节点列表但不符合 第 2.3.5.1 节中语法的 JSONPath 表达式不是单一查询。

1.1.1. JSON 值作为 节点树

本文档将查询实参建模为一棵 JSON 值树, 每个 JSON 值 都有自己的节点。 节点要么是根节点,要么是其后代之一。

本文档将把查询应用于 查询实参的结果建模为节点列表(节点的列表)。

节点是查询实参中可选择的部分。 对象中唯一可由查询选择的部分是 成员值。成员名和成员(名称/值对)不能被 选择。 因此,成员值有节点,但成员和成员名没有。 类似地,成员值是对象的子节点,但成员和 成员名不是。

1.2. 历史

本文档基于 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 查询实参中节点的 路径 组成。

1.3. JSON 值

JSONPath 查询所应用到的 JSON 值,按定义,是一个 有效的 JSON 值。JSON 值通常通过解析 JSON 文本构造。

将 JSON 文本解析为 JSON 值,以及当 JSON 文本不表示有效 JSON 时会发生什么,不由本文档定义。 [RFC8259] 的第 4 节和第 8指出了可能 符合 JSON 文本语法但不属于可互操作使用的 特定情形,因为它们可能导致不可预测的行为。 本文档不试图为这些情形下的 JSONPath 查询定义可预测 行为。

具体而言,第 2.3.12.3.22.3.52.5.2 节的“语义”小节描述的行为, 在所考虑的某个对象的 JSON 值 是由呈现如下情况的 JSON 文本构造时会变得不可预测: 单个对象中存在多个共享同一成员名的成员 (“重复名称”;见[RFC8259] 的 第 4 节)。 此外,在按名称选择子节点(第 2.3.1 节)和比较字符串 (第 2.3.5.2.2 节)时,假定这些 字符串是 Unicode 标量值序列;如果不是,则行为会变得不可预测 ([RFC8259] 的 第 8.2 节)。

1.4. JSONPath 表达式概述

JSONPath 表达式应用于一个 JSON 值,该值称为查询实参。 输出是节点列表。

JSONPath 表达式由一个标识符后跟一系列 零个或多个段组成,每个段包含一个或多个选择器。

1.4.1. 标识符

根节点标识符 $ 引用 查询实参的根节点, 即引用该实参整体。

当前节点标识符 @ 引用 过滤器表达式求值语境中的当前 节点(第 2.3.5 节)。

1.4.2.

段选择输入值的子节点([<selectors>])或 后代(..⁠[<selectors>])。

段可以使用 方括号表示法,例如:

$['store']['book'][0]['title']

也可以使用更紧凑的 点表示法,例如:

$.store.book[0].title

方括号表示法包含一个或多个(逗号分隔的)任意种类的 选择器。 选择器将在下一节详细说明。

JSONPath 表达式可以组合使用方括号表示法和点 表示法。

本文档将方括号表示法视为规范形式,并以 方括号表示法定义简写的点表示法。示例和描述会在方便时使用简写。

1.4.3. 选择器

名称选择器,例如 'name',选择对象的具名子节点。

索引选择器,例如 3,选择数组的带索引子节点。

在表达式 [*] 中,通配符 *第 2.3.2 节)选择 一个节点的所有子节点;在表达式 ..[*] 中,它选择一个节点的所有后代。

数组切片 start:end:step第 2.3.4 节)从数组中选择一系列 元素,给定起始位置、结束位置以及 一个可选的步长值,用于使位置从起点移动到 终点。

过滤器表达式 ?<logical-expr> 选择 对象或数组的某些子节点,如下所示:

$.store.book[?@.price < 10].title

1.4.4. 总结

表 1 简要概述了 JSONPath 语法。

表 1JSONPath 语法概述
语法元素 描述
$ 根节点 标识符第 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 节: 在过滤器表达式中调用函数

1.5. JSONPath 示例

本节为资料性内容。它提供 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
    }
  }
}
图 1示例 JSON 值

表 2 展示了 可应用于此示例的一些 JSONPath 查询及其预期结果。

表 2应用于示例 JSON 值时的 示例 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 的书
$..* 输入值中包含的所有成员值和数组元素

2. JSONPath 语法和语义

2.1. 概述

JSONPath 表达式是一个字符串,当应用于 JSON 值 (查询实参)时,它会选择该实参的零个或多个节点,并将 这些节点作为节点列表输出。

查询MUST使用 UTF-8 编码。 本文档给出的查询语法假定其 UTF-8 形式首先按照 [RFC3629] 中所述解码为 Unicode 标量值;能够得到等价 结果的实现方法也是可行的。

用作 JSONPath 查询的字符串需要是格式良好有效的。 如果字符串符合本文档中的 ABNF 语法,则它是格式良好的 JSONPath 查询。 如果格式良好的 JSONPath 查询还满足本文档提出的两个语义 要求,则它是有效的;这些要求如下:

  1. JSONPath 查询中与 JSONPath 处理相关的 整数(例如索引值和步长)MUST 位于 Internet JSON(I-JSON) 中定义的精确整数值范围内(见 [RFC7493] 的 第 2.2 节 ),即位于区间 [-(253)+1, (253)-1] 内。
  2. 函数扩展的使用MUST良类型的, 如第 2.4.3 节所述。

JSONPath 实现对于任何不是 格式良好且有效的查询MUST引发错误。 JSONPath 查询的格式良好性和有效性独立于 该查询所应用到的 JSON 值。在将查询应用于值的过程中, 不能再引发与 JSONPath 查询的 格式良好性和有效性相关的错误。 这清楚地把查询中的格式良好性/有效性错误 与可能实际源自数据缺陷的不匹配区分开来。

有效查询所期望的结构 与数据中发现的结构之间的不匹配,可能导致空查询结果, 这可能出乎意料,并指示二者之一存在缺陷。 因此,JSONPath 实现可能希望向应用开发者提供诊断信息, 以帮助找出空 结果的原因。

显然,实现仍可能在执行 JSONPath 查询时失败,例如由于资源耗尽,但本文档不对此建模。 然而,实现MUST NOT 静默失常。具体而言,如果一个有效的 JSONPath 查询 针对某个结构化值求值,而该值的大小过大,以至于无法 正确处理该查询(例如需要处理超出精确值范围的 数字),则实现 MUST提供溢出指示。

(熟悉 HTTP 错误模型的读者,在思考格式良好性和有效性时, 可能会联想到 400 类错误;并可能会把资源耗尽及相关错误识别为可类比于 500 类 错误。)

2.1.1. 语法

从语法上看,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

段的语法和语义定义见第 2.5 节

2.1.2. 语义

在本文档中,JSONPath 查询的语义定义了 所要求的结果,而不规定实现的内部工作方式。 本文档可以用过程化的逐步方式描述语义;然而, 这类描述仅在任何实现MUST产生相同结果这一意义上是规范性的, 而不是要求实现者使用相同算法。

语义是:有效查询针对一个值 (查询实参)执行,并产生一个节点列表(即该值的零个或多个 节点组成的列表)。

查询是一个根标识符后跟零个或 多个段的序列,其中每个段都应用于前一个根标识符或段的结果, 并为下一个段提供输入。 这些结果和输入都采用节点列表的形式。

根标识符产生的节点列表包含单个 节点 (即查询实参)。 最后一个段产生的节点列表作为查询 结果呈现。根据具体 API,它可能 呈现为节点处 JSON 值的数组、引用这些节点的 规范化路径数组,或二者兼有——也可以是实现所需的 某种其他表示。 注:空节点列表是有效的查询结果。

段依次作用于其输入节点列表中的每个节点, 并按派生这些结果的输入 节点列表顺序,将所得节点列表连接起来,以产生 该段的结果。一个节点可以被选择多次,并在节点列表中出现 相应次数。重复节点不会被移除。

语法有效的段在执行查询时MUST NOT 产生错误。 这意味着一些可能被认为是错误的 操作,例如使用超出数组范围的索引, 只会导致选择更少的节点。 (关于此属性的更多讨论可见第 2.1 节引言。)

这种方法的一个结果是,如果任一段 产生空节点列表,则整个查询也会产生空 节点列表。

如果查询的语义给实现留下了 产生多种可能排序的选择,则某个特定实现 可以在连续多次运行该查询时产生不同的排序。

2.1.3. 示例

考虑这个示例。给定查询实参 {"a":[{"b":0},{"b":1},{"c":2}]}, 查询 $.a[*].b 选择以下节点列表(此处用它们的 值表示):01

该查询由 $ 后跟三个段组成: .a[*].b

首先,$ 产生一个仅由 查询实参组成的节点列表。

接下来,.a 从任何对象输入节点中选择, 并选择输入 节点中与成员名 "a" 相对应的任何 成员值的节点。 结果仍是一个包含单个节点的列表: [{"b":0},{"b":1},{"c":2}]

接下来,[*] 从输入数组节点中选择 所有元素。 结果是三个节点的列表:{"b":0}{"b":1}{"c":2}

最后,.b 从任何具有成员名 b 的对象输入节点中选择,并选择输入节点中与该名称相对应的 成员值节点。 结果是包含 01 的列表。 这是三个列表的连接:两个长度为一的列表分别包含 01,以及一个长度为零的列表。

2.2. 根标识符

2.2.1. 语法

每个 JSONPath 查询(过滤器表达式内部的查询除外;见第 2.3.5 节MUST 以根标识符 $ 开始。

root-identifier     = "$"

2.2.2. 语义

根标识符 $ 表示 查询实参的根节点, 并产生一个由该根节点组成的节点列表。

2.2.3. 示例

JSON:

{"k": "v"}

查询:

表 3根标识符 示例
查询 结果 结果路径 注释
$ {"k": "v"} $ 根节点

2.3. 选择器

选择器只出现在 子段第 2.5.1 节后代段第 2.5.2 节内。

选择器会产生一个由输入值的零个或多个子节点组成的节点列表。

有多种选择器可产生对象的子节点、 数组的子节点, 或对象和数组二者的子节点。

selector            = name-selector /
                      wildcard-selector /
                      slice-selector /
                      index-selector /
                      filter-selector

每种选择器的语法和语义定义如下。

2.3.1. 名称选择器

2.3.1.1. 语法

名称选择器 '<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")。
  • 带引号字符串中的字母字符在 ABNF 中不区分大小写, 因此 \u 转义中的每个十六进制数字(如 hexchar 引用的规则所指定)可以是小写或大写, 而 \u 中的 u 需要是小写(表示为 %x75)。
2.3.1.2. 语义

name-selector 字符串MUST通过移除外围引号并 将每个转义序列替换为其等价 Unicode 字符,转换为 成员名 M,如 表 4 所示:

表 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 中的成员名字符串应用规范化操作。

2.3.1.3. 示例

JSON:

{
  "o": {"j j": {"k.k": 3}},
  "'": {"@": 2}
}

查询:

表 5 中的示例展示了名称选择器在 子段中的使用。

表 5名称选择器 示例
查询 结果 结果路径 注释
$.o['j j'] {"k.k": 3} $['o']['j j'] 嵌套
对象中的
具名
$.o['j j']⁠['k.k'] 3 $['o']['j j']⁠['k.k'] 进一步
向下
嵌套
$.o["j j"]⁠["k.k"] 3 $['o']['j j']⁠['k.k'] 查询中
分隔符
不同,
规范化
路径
不变
$["'"]["@"] 2 $['\'']['@'] 不寻常的
成员
名称

2.3.2. 通配符选择器

2.3.2.1. 语法

通配符选择器由一个星号组成。

wildcard-selector   = "*"
2.3.2.2. 语义

通配符选择器选择 对象或数组的所有子节点。 对象的子节点在所得节点列表中出现的顺序 未作规定, 因为 JSON 对象是无序的。 数组的子节点在所得节点列表中按数组顺序出现。

注意,对象的子节点是其成员值, 而不是其成员名。

通配符选择器不会从基本 JSON 值(即 数字、字符串、truefalsenull)中选择任何内容。

2.3.2.3. 示例

JSON:

{
  "o": {"j": 1, "k": 2},
  "a": [5, 3]
}

查询:

表 6 中的示例展示了通配符选择器在 子段中的使用。

表 6通配符 选择器示例
查询 结果 结果路径 注释
$[*] {"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[*, *] 的示例 表明,当通配符选择器应用于具有两个或更多 成员的对象节点时,它每次出现在子段中都可能产生顺序不同的 节点列表 (但当它应用于成员少于两个的对象节点或数组节点时不会如此)。

2.3.3. 索引选择器

2.3.3.1. 语法

索引选择器 <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 节)。

注:

  • index-selector 是一个 整数(十进制,与 JSON 数字相同)。
  • 与 JSON 数字一样,该语法不 允许带前导零的八进制样式整数,例如 01-01
2.3.3.2. 语义

应用于数组的非负 index-selector 使用从零开始的索引选择数组元素。 例如,选择器 0 选择第一个元素,选择器 4 选择足够长数组的第五个元素。 如果索引位于数组范围之外,则不会选择任何内容,且这不是错误。 对于不是数组的值,不会选择任何内容。

负的 index-selector 从 数组末尾向后计数, 通过将数组长度加到负索引上获得等价的非负 index-selector。 例如,选择器 -1 选择最后一个元素,选择器 -2 选择至少有两个 元素的数组的倒数第二个元素。 与非负索引一样,如果这样的元素 不存在,也不是错误;这只意味着没有元素被选择。

2.3.3.3. 示例

JSON:

["a","b"]

查询:

表 7 中的示例展示了索引选择器在 子段中的使用。

表 7索引选择器 示例
查询 结果 结果路径 注释
$[1] "b" $[1] 数组元素
$[-2] "a" $[0] 数组元素,从 末尾计数

2.3.4. 数组切片 选择器

2.3.4.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

切片选择器由三个可选的十进制 整数组成,以冒号分隔。 当第三个整数省略时,第二个冒号可以省略。

为保持有效,所提供的整数MUST位于 I-JSON 精确值范围内(见第 2.1 节)。

2.3.4.2. 语义

切片选择器的灵感来自 曾为 ECMAScript 4(ES4)提出但从未发布的切片运算符, 以及 Python 的切片运算符。

2.3.4.2.1. 非正式引言

本节为资料性内容。

数组切片的灵感来自 JavaScript 语言中 Array.prototype.slice 方法的行为, 该行为由 ECMA-262 标准 [ECMA-262] 定义; 同时加入了 step 参数,其灵感 来自 Python 切片表达式。

数组切片表达式 start:end:step 选择从索引 start 开始、 按 step 递增、并以 end 结束(end 本身被排除)的元素。 因此,例如表达式 1:3(其中 step 默认为 1) 选择索引为 12 的元素(按该 顺序),而 1:5:2 选择索引为 13 的元素。

step 为负时,元素按 反向顺序选择。因此, 例如 5:1:-2 选择索引为 53 的元素(按 该顺序),而 ::-1 选择数组中的所有元素, 并按 反向顺序排列。

step0 时,不选择 任何元素。 (这是与 Python 行为不同的唯一情况;Python 在这种情况下 会引发错误。)

下一节完整规定了该行为, 而不依赖于 JavaScript 或 Python 的行为。

2.3.4.2.2. 规范性语义

切片表达式按与数组相同的顺序 或反向顺序,选择输入数组中 元素的一个子集, 具体取决于 step 参数的符号。 它不会从非数组节点中选择任何节点。

切片由两个切片参数 startend,以及 一个迭代增量 step 定义。 这些参数均为 可选。在本节余下部分,len 表示 输入数组的长度。

step 的默认值为 1startend 的默认值取决于 step 的符号, 如表 8所示。

表 8默认数组切片 start 和 end 值
条件 start end
step >= 0 0 len
step < 0 len - 1 -len - 1

切片表达式参数 startend 不能直接用作 切片边界,必须先被规范化。 此目的下的规范化定义为:

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 的结果。

切片表达式参数 startend 用于推导切片边界 lowerupper。 由 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 时,不选择 任何元素,结果数组为空。

2.3.4.3. 示例

JSON:

["a", "b", "c", "d", "e", "f", "g"]

查询:

表 9 中的示例展示了数组切片选择器在 子段中的使用。

表 9数组切片 选择器示例
查询 结果 结果路径 注释
$[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]
反向切片

2.3.5. 过滤器选择器

过滤器选择器用于迭代结构化值的元素或成员, 即 JSON 数组和对象。 这些结构化值由使用过滤器选择器的 子段或后代段所提供的节点列表标识。

对于每次迭代(元素/成员),会对一个逻辑表达式(即 过滤器表达式) 进行求值,该表达式决定 元素/成员的节点是否被选择。 (虽然逻辑表达式求值结果在数学上是 布尔值,但本规范使用术语 logical,以保持它与 JSON 能表示的布尔值之间的 区别。)

在迭代过程中,过滤器表达式会接收 正在过滤的结构化值中每个数组元素或对象成员值的 节点;该元素或成员值随后称为当前节点

当前节点可以作为过滤器表达式子表达式中一个或多个 JSONPath 查询的起点, 通过当前节点标识符 @ 记写。 每个 JSONPath 查询既可用于测试查询结果 是否存在,也可用于获取该查询产生的特定 JSON 值, 该值随后可用于比较,或作为 函数实参

过滤器选择器可以使用函数扩展,见 第 2.4 节。 在过滤器选择器的逻辑表达式中,函数 表达式可用于操作节点列表和值。 可用函数集合是可扩展的,其中预定义了若干 函数(见第 2.4 节), 并且“函数扩展”子注册表(第 3.2 节)提供了注册更多 函数的能力。 定义函数时,会为其赋予唯一名称,并为其返回值及每个 形参赋予 声明类型。 类型系统的范围有限;其目的是表达 在没有函数时隐含于 过滤器表达式语法中的限制。 该类型系统还指导转换(第 2.4.2 节),这些转换模拟 未使用函数表达式时语法处理不同类型表达式的 方式。

2.3.5.1. 语法

过滤器选择器的形式为 ?<logical-expr>

filter-selector     = "?" S logical-expr

由于过滤器表达式由无副作用的组成部分 构成, 求值顺序不需要(也未)定义。 类似地,对于合取(&&)和析取 (||)(稍后定义), 短路求值实现和完全求值 实现都会得到相同结果;因此这两种实现 策略都是有效的。

当前节点可通过当前节点 标识符 @ 访问。 该标识符指向直接包围它的 filter-selector 的当前节点。 注:在嵌套的 filter-selector 中,没有语法可寻址除直接包围它的 filter-selector 之外的任何当前节点(即无法寻址 包围直接 包围该标识符的 filter-selector 的那些 filter-selector 的当前节点)。

逻辑表达式提供通常的布尔运算符 (|| 表示 OR, && 表示 AND,! 表示 NOT)。 它们具有布尔代数的通常语义,并遵守其定律 (例如,见 [BOOLEAN-LAWS])。 括号MAYlogical-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 = "@"

比较表达式可用于比较 基本 值(即数字、字符串、truefalsenull)。 这些值可以通过字面量值获得;也可以通过单一查询获得,其中每个 单一查询至多选择一个节点,随后使用该节点的值;还可以通过 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”。

truefalsenull 只能为小写(区分大小写)。

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 按优先级从最高 (结合最紧)到最低(结合最松)的顺序列出了过滤器表达式运算符。

表 10过滤器 表达式运算符优先级
优先级 运算符类型 语法
5 分组
函数表达式
(...)
name(...)
4 逻辑 NOT !
3 关系 == !=
< <= > >=
2 逻辑 AND &&
1 逻辑 OR ||
2.3.5.2. 语义

过滤器选择器专用于数组和对象。 其结果分别是它们的数组元素或成员值中的(零个一个多个全部)列表。 应用于基本值时,它不选择任何内容(因此不会 对过滤器选择器的结果作出贡献)。

在所得节点列表中,数组的子节点 按其在数组中的位置排序。 对象(相对于数组而言)的子节点 在所得节点列表中出现的顺序未作规定, 因为 JSON 对象是无序的。

2.3.5.2.1. 存在性测试

查询本身位于逻辑上下文中时,就是 存在性测试:如果查询选择至少一个节点,则产生 true; 如果查询不选择任何节点,则产生 false。

存在性测试与比较不同之处在于:

  • 它们可用于任意 相对或绝对查询(不仅仅是单一查询)。
  • 它们可用于选择 结构化值的查询。

若要检查查询所选择节点的值, 必须使用显式比较。 例如,若要测试查询 @.foo 所选择的节点是否具有值 null,请使用 @.foo == null(见第 2.6 节), 而不是取反的存在性测试 !@.foo(如果 @.foo 选择了一个节点,无论该节点的 值为何,它都会产生 false)。 类似地,@.foo == false 只有在 @.foo 选择了一个节点且 该节点的值为 false 时才会产生 true。

2.3.5.2.2. 比较

首先定义比较运算符 ==<,随后用它们来定义 !=<=>>=

当比较任一侧的结果为空 节点列表,或为特殊结果 Nothing(见第 2.4.1 节)时:

  • 使用运算符 == 的比较,当且仅当 另一侧也产生空节点列表或特殊结果 Nothing 时,产生 true。
  • 使用运算符 < 的比较产生 false。

当比较任一侧的任何查询或函数表达式 产生由单个节点组成的节点列表时, 该侧会 被替换为其节点的值,然后:

  • 使用运算符 == 的比较,当且仅当比较 发生在以下对象之间时,产生 true:

    • 预期可互操作的数字, 按照 I-JSON [RFC7493] 的 第 2.2 节,使用通常的数学相等关系比较相等,
    • 数字,其中 至少一个按 I-JSON 不预期可互操作,且这些数字使用 实现特定的相等关系比较为相等,
    • 相等的 非数字基本值,
    • 相等的数组, 即长度相同且第一个数组中每个元素都等于第二个数组中对应 元素的数组,或
    • 没有重复名称的相等对象, 即满足:

      • 两个对象具有相同的名称集合(无 重复),并且
      • 对于这些名称中的每一个,对象中与 该名称关联的值都相等。
  • 使用运算符 < 的比较,当且仅当 比较发生在同为数字或同为 字符串并满足该比较的值之间时,产生 true:

    • 预期可互操作的数字, 按照 I-JSON [RFC7493] 的 第 2.2 节MUST 使用通常的 数学排序进行比较; 按 I-JSON 不预期可互操作的数字,MAY 使用 实现特定的排序进行比较,
    • 空 字符串小于任何非空字符串,并且
    • 一个非空 字符串小于另一个非空字符串,当且 仅当第一个字符串以比第二个字符串更低的 Unicode 标量值开头,或者两个 字符串以相同的 Unicode 标量值开头且 第一个字符串的剩余部分小于 第二个字符串的剩余部分。

!=<=>>= 根据其他比较运算符定义。 对于任意 ab

  • 比较 a != b 当且仅当 a == b 产生 false 时产生 true。
  • 比较 a <= b 当且仅当 a < b 产生 true 或 a == b 产生 true 时产生 true。
  • 比较 a > b 当且仅当 b < a 产生 true 时产生 true。
  • 比较 a >= b 当且仅当 b < a 产生 true 或 a == b 产生 true 时产生 true。
2.3.5.3. 示例

第一组示例展示了一些比较 表达式及其在给定 JSON 值作为输入时的 结果。

JSON:

{
  "obj": {"x": "y"},
  "arr": [2, 3]
}

比较:

表 11比较 示例
比较 结果 注释
$.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"
}

查询:

表 12 中的示例展示了过滤器选择器在 子段中的使用。

表 12过滤器选择器 示例
查询 结果 结果路径 注释
$.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] 的示例表明,过滤器选择器每次出现在子段中时, 可能产生顺序不同的节点列表。

2.4. 函数扩展

除前面小节中定义的过滤器表达式功能之外, 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 节)。 为定义哪些函数表达式是良类型的, 首先引入一个类型系统。

2.4.1. 函数表达式的 类型系统

函数扩展的每个形参和结果都必须具有 声明类型。

声明类型使得可以独立于 JSONPath 查询所应用到的任何查询实参, 检查 JSONPath 查询的良类型性。

表 13 根据 所包含的实例定义了可用类型。

表 13函数扩展 类型系统
类型 实例
ValueType JSON 值或 Nothing
LogicalType LogicalTrueLogicalFalse
NodesType 节点列表

注:

  • 唯一能够在 JSONPath 语法中直接 表示的实例,是 ValueType 中以字面量表示的某些 JSON 值 (在 JSONPath 中限于基本值)。
  • 特殊结果 Nothing 表示 JSON 值的缺失,并且不同于任何 JSON 值, 包括 null
  • LogicalTrueLogicalFalse 与 字面量 truefalse 所表示的 JSON 值无关。

2.4.2. 类型转换

正如查询可通过测试至少一个节点 是否存在(第 2.3.5.2.1 节)而用于逻辑表达式一样, 声明类型为 NodesType 的函数表达式也可作为 声明类型为 LogicalType 的形参的函数实参,并采用等价的转换 规则:

  • 如果节点列表包含一个或多个节点, 转换结果为 LogicalTrue
  • 如果节点列表为空,则转换 结果为 LogicalFalse

注:

  • 从节点列表中提取值可以通过多种 方式完成,因此从 NodesTypeValueType 的隐式转换 可能令人意外,因此未定义这种转换。
  • 声明类型为 NodesType 的函数表达式,可以 通过把该表达式包装到某个函数扩展调用中, 间接用作声明类型为 ValueType 的形参的实参,例如 value()(见第 2.4.8 节), 该函数接受 NodesType 类型的形参并返回 ValueType 类型的结果。

现在可以依据此类型系统定义函数表达式的 良类型性。

2.4.3. 函数表达式的 良类型性

对于函数表达式而言,要成为良类型:

  1. 其声明类型在其出现的上下文中必须是良类型的。

    根据语法,函数表达式可以出现在 三种不同的直接上下文中,这些上下文会导出以下良类型性条件:

    作为逻辑表达式中的 test-expr

    该函数的声明结果类型为 LogicalType,或 (按第 2.4.2 节引起转换) 为 NodesType

    作为比较中的 comparable

    该函数的声明结果类型为 ValueType

    作为另一个函数表达式中的 function-argument

    该函数的声明结果类型满足 以下针对外围函数相应形参的规则。

  2. 其实参必须对相应形参的声明类型 是良类型的。

    当函数的每个实参都能根据以下 条件之一用于相应形参的声明类型时, 该函数表达式的实参是良类型的:

    • 当该实参是一个函数 表达式,且其声明结果类型与 形参的声明类型相同时。
    • 当形参的声明类型为 LogicalType 且实参为以下之一时:

      • 声明结果类型为 NodesType 的函数表达式。 在这种情况下,实参按第 2.4.2 节转换为 LogicalType。
      • 不是函数表达式的 logical-expr
    • 当形参的声明类型为 NodesType 且实参是查询 (包括单一查询)时。
    • 当形参的声明类型为 ValueType 且实参为以下之一时:

      • 以 字面量表示的值。
      • 单一查询。在这种 情况下:

        • 如果 查询结果是由单个节点组成的节点列表,则 实参为该节点的值。
        • 如果 查询结果为空节点列表,则实参为 特殊结果 Nothing

2.4.4. length() 函数扩展

形参:
  1. ValueType
结果:

ValueType(无符号整数或 Nothing

length() 函数扩展提供了一种 计算值的长度 并使其可供过滤器表达式中进一步处理的方式:

$[?length(@.authors) >= 5]

它的唯一实参是 ValueType 的实例(可能 来自 单一查询,如上例所示)。结果也是 ValueType 的实例:无符号整数或特殊结果 Nothing

  • 如果实参值是字符串,则结果 是该字符串中的 Unicode 标量值数量。
  • 如果实参值是数组,则结果 是数组中的元素数量。
  • 如果实参值是对象,则结果 是对象中的成员数量。
  • 对于任何其他实参值,结果为 特殊结果 Nothing

2.4.5. count() 函数扩展

形参:
  1. NodesType
结果:

ValueType(无符号整数)

count() 函数扩展提供了一种获取 节点列表中节点数量 并使其可供过滤器表达式中进一步处理的方式:

$[?count(@.*.author) >= 5]

它的唯一实参是节点列表。 结果是一个值(无符号整数),给出 节点列表中的节点数量。

注:

  • 不会对节点列表进行去重。
  • 节点列表中的节点数量独立于它们的 值或它们可能具有的任何子节点来计数;例如,非空 单一节点列表(如 count(@))的计数始终为 1。

2.4.6. match() 函数扩展

形参:
  1. ValueType(字符串)
  2. ValueType(符合 [RFC9485] 的字符串)
结果:

LogicalType

match() 函数扩展提供了一种检查 给定字符串是否(整体;见第 2.4.7 节)匹配给定 正则表达式的方式,该正则表达式采用 [RFC9485] 中描述的形式。

$[?match(@.date, "1974-05-..")]

其实参是 ValueType 的实例(可能 来自 单一查询,如上例中的第一个实参)。 如果第一个实参不是字符串,或第二个实参不是 符合 [RFC9485] 的字符串, 则结果为 LogicalFalse。 否则,第一个实参所对应的字符串会与 第二个实参所对应字符串中包含的 I-Regexp 进行匹配; 如果该字符串匹配该 I-Regexp,则结果为 LogicalTrue,否则为 LogicalFalse

2.4.8. value() 函数扩展

形参:
  1. NodesType
结果:

ValueType

value() 函数扩展提供了一种将 NodesType 的实例转换为值,并 使其可供过滤器表达式中进一步处理的方式:

$[?value(@..color) == "red"]

它的唯一实参是 NodesType 的实例(可能 来自 filter-query,如上例所示)。结果是 ValueType 的实例。

  • 如果实参包含单个节点,则 结果为该节点的值。
  • 如果实参为空节点列表或 包含多个节点,则结果为 Nothing

注:单一查询可用于任何预期 ValueType 的位置, 因此无需对单一查询使用 value() 函数扩展。

2.4.9. 示例

表 14函数表达式 示例
查询 注释
$[?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(@.*)] 对于任何形参声明类型为 NodesTypeLogicalType 且结果类型为 LogicalType 的函数 bnl(),都是良类型
$[?blt(1==1)] 良类型,其中 blt() 是形参声明类型为 LogicalType 且结果类型为 LogicalType 的函数
$[?blt(1)] 对于同一个 函数 blt(),非良类型,因为 1 不是查询、 logical-expr 或函数表达式
$[?bal(1)] 良类型,其中 bal() 是形参声明类型为 ValueType 且结果类型为 LogicalType 的函数

2.5.

对于输入节点列表中的每个节点, 段会将一个或多个选择器应用于该节点,并把 每个选择器的结果连接为按输入节点划分的节点列表,然后再 按输入节点列表的顺序将这些节点列表连接起来,形成单个 段结果节点列表。

结果表明,查询中的段越多, 所得节点列表中节点在输入值中的 深度就越大:

  • 具有 N 个段的查询,其中 N >= 0,会产生一个 节点列表, 其中包含输入值中深度为 N 或更大的节点。
  • 具有 N 个段的查询,其中 N >= 0,且所有段 都是子段第 2.5.1 节, 会产生一个由输入值中深度恰好为 N 的节点组成的节点列表。

段有两种:子段和后代段。

segment             = child-segment / descendant-segment

每种段的语法和语义定义如下。

2.5.1. 子段

2.5.1.1. 语法

子段由一个非空的、逗号分隔的 选择器序列组成,该序列括在方括号中。

当只有单个 通配符选择器或名称选择器时,也提供了简写记法。

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'] 的简写)。

2.5.1.2. 语义

子段包含一个选择器序列,其中每个 选择器 都会选择输入值的零个或多个子节点。

不同种类的选择器可以在 单个子段内组合使用。

对于输入节点列表中的每个节点, 子段的所得节点列表是按照选择器在列表中出现的顺序, 连接其每个选择器产生的 节点列表而得到的结果。 注:任何被多个选择器匹配的节点都会在 节点列表中保留相应次数。

在某个选择器能够以多种 可能顺序产生节点列表的情况下, 该选择器在子段中的每次出现 都可能产生顺序不同的节点列表。

总之,子段会向输入值的结构中 再向下深入一级。

2.5.1.3. 示例

JSON:

["a", "b", "c", "d", "e", "f", "g"]

查询:

表 15子段 示例
查询 结果 结果路径 注释
$[0, 3] "a"
"d"
$[0]
$[3]
索引
$[0:2, 5] "a"
"b"
"f"
$[0]
$[1]
$[5]
切片和索引
$[0, 0] "a"
"a"
$[0]
$[0]
重复条目

2.5.2. 后代段

2.5.2.1. 语法

后代段由双点号 .. 后跟一个子段(使用方括号记法)组成。

也提供了与子段简写形式相对应的 简写记法。

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 所指定。

注:单独的 .. 不是有效的 段。

2.5.2.2. 语义

后代段会产生输入值的零个或多个后代。

对于输入节点列表中的每个节点, 后代选择器会访问该输入节点及其每一个 后代,并满足:

  • 任何数组的节点都按 数组顺序访问,并且
  • 节点会先于其 后代被访问。

对象的子节点被访问的顺序 未作规定,因为 JSON 对象是无序的。

假设后代段的形式为 ..⁠[<selectors>](在将任何简写 形式转换为方括号记法之后), 并且按访问顺序排列的节点为 D1、...、Dn (其中 n >= 1)。 注:D1 是输入值。

对于每个满足 1 <= i <= ni,节点列表 Ri 被定义为 将子段 [<selectors>] 应用于节点 Di 的结果。

对于输入节点列表中的每个节点, 后代段的结果是 R1、 ...、Rn(按该顺序)的连接。 然后这些结果再按输入节点列表顺序连接,形成 该段的结果。

总之,后代段会向每个输入值的结构中 向下深入一级或多级。

2.5.2.3. 示例

JSON:

{
  "o": {"j": 1, "k": 2},
  "a": [5, 3, [{"j": 4}, {"k": 6}]]
}

查询:

(注意,第四个示例可以用两个 等价 查询表示,它们在表 16中显示在同一表行中,而不是两个 几乎相同的行中。)

表 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} 必须出现在 12 之前,
  • [5, 3, [{"j": 4}, {"k": 6}]] 必须出现在 53[{"j": 4}, {"k": 6}] 之前,
  • 5 必须出现在 3 之前,而 3 必须出现在 [{"j": 4}, {"k": 6}] 之前,
  • 53 必须出现在 {"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 实现就是这种情况)。

2.6. null 的语义

注:JSON null 与任何其他 JSON 值一样处理, 即它不被视为表示“未定义”或“缺失”。

2.6.1. 示例

JSON:

{"a": null, "b": [null], "c": [{}], "null": 1}

查询:

表 17涉及 (或不涉及)null 的示例
查询 结果 结果路径 注释
$.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, 只是成员名字符串

2.7. 规范化路径

规范化路径是值中节点位置的唯一表示, 可在该值中唯一标识该节点。 具体而言,规范化路径是一种具有受限语法(定义如下)的 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)。

2.7.1. 示例

表 18规范化路径 示例
路径 规范化路径 注释
$.a $['a'] 对象值
$[1] $[1] 数组索引
$[-3] $[2] 长度为 5 的数组中的负 数组索引
$.a.b[1:2] $['a']['b'][1] 嵌套结构
$["\u000B"] $['\u000b'] Unicode 转义
$["\u0061"] $['a'] Unicode 字符

3. IANA 注意事项

3.1. 媒体类型 application/jsonpath 的注册

IANA 已注册以下媒体类型 [RFC6838]

类型名称:

application

子类型名称:

jsonpath

必需参数:

不适用

可选参数:

不适用

编码注意事项:

二进制(UTF-8)

安全注意事项:

见 RFC 9535 的安全注意事项一节。

互操作性注意事项:

不适用

已发布规范:

RFC 9535

使用此媒体类型的应用:

需要在 JSON 数据中传达查询的应用

片段标识符注意事项:

不适用

附加信息:


此类型的已弃用别名:

不适用

魔数:

不适用

文件扩展名:

不适用

Macintosh 文件类型代码:

不适用

联系人及其电子邮件地址,用于获取更多信息:
iesg@ietf.org
预期用途:

COMMON

使用限制:

不适用

作者:

JSONPath 工作组

变更控制方:

IETF

3.2. 函数扩展 子注册表

根据本规范,IANA 已在 新的“JSONPath”注册表中创建新的“Function Extensions” 子注册表。“Function Extensions”子注册表采用“专家审查”策略 ([RFC8126] 的 第 4.5 节 )。

专家被指示要谨慎分配那些暗示一般适用语义的函数 扩展名称, 将它们保留给可能被广泛 使用且能充分利用其简洁性的函数。 专家还被指示要求注册人提供一份 规范([RFC8126] 的 第 4.6 节),但可以作出例外, 例如,当注册时尚无可用规范但很可能即将 提供时。 如果专家了解到已有函数扩展被部署并 使用,若其认为此类注册可避免潜在的未来冲突, 也可以自行发起注册。

子注册表中的每个条目都必须包含以下内容:

函数名称:

一个小写 ASCII [RFC0020] 字符串,以字母开头,之后可以 包含字母、数字和下划线字符 ([a-z][_a-z0-9]*)。子注册表中的其他条目不能具有 相同的函数名。

简要描述:

简要描述

形参:

零个或多个声明类型的逗号分隔列表,每个类型对应 此函数扩展所预期的一个 实参

结果:

此函数扩展结果的声明类型

变更控制方:

[RFC8126] 的 第 2.3 节

参考:

提供该函数扩展描述的参考文档

此子注册表的初始条目列于表 19;“Change Controller”列中的 条目均具有值“IETF”, 而“Reference”列中的条目 均具有值“RFC 9535 的第 2.4 节”:

表 19函数扩展子注册表中的 初始条目
函数名称 简要描述 形参 结果
length 字符串、数组或对象的长度 ValueType ValueType
count 节点列表大小 NodesType ValueType
match 正则表达式完整匹配 ValueType, ValueType LogicalType
search 正则表达式子字符串匹配 ValueType, ValueType LogicalType
value 节点列表中单个节点的值 NodesType ValueType

4. 安全注意事项

JSONPath 的安全注意事项可源自:

4.1. 针对 JSONPath 实现的攻击向量

历史上,JSONPath 常常通过将查询的部分内容传递给 底层编程语言引擎来实现,例如 JavaScript 的 eval() 函数。 众所周知,这种方法会导致注入攻击,并且 需要完美的输入验证才能防止这些攻击(关于 JSON 本身的类似考虑,见 [RFC8259] 的 第 12 节)。 相反,JSONPath 实现需要实现查询的完整语法, 而不依赖编程语言 引擎的解析器。

针对可用性的攻击可能试图触发某些实现 在某些情况下表现出的异常昂贵的运行时性能。 (关于哈希表实现中的问题,见 [RFC8949] 的 第 10 节, 关于正则表达式实现中的性能问题,见 [RFC9485] 的 第 8 节。) 实现者需要意识到,只要攻击者能够选择提交特制的 JSONPath 查询或查询实参,从而触发出人意料地高、甚至可能呈 指数级的 CPU 使用率,或者例如通过对后代段的朴素递归实现导致 栈溢出,那么良好的平均性能就 不足够。实现需要具有适当的资源管理 来缓解这些攻击。

4.2. 针对 JSONPath 查询形成方式的攻击向量

JSONPath 查询通常不是静态的,而是由提供索引值、 成员名或过滤器表达式中用于比较的值的变量 形成。 这些变量需要经过验证(例如,仅在给定值允许时才允许形成 .name 之类的特定构造)和转换 (例如,通过转义字符串分隔符)。 未正确执行这些验证和转换,可能导致意外 失败,进而可能导致可用性、机密性和 完整性破坏,尤其是在对手能够控制这些 值时(例如,通过把它们输入到 Web 表单中)。 由此产生的攻击类别,即注入(例如 SQL 注入), 一直位列应用安全 漏洞的主要原因之中,需要特别关注。

4.3. 针对采用 JSONPath 的安全 机制的攻击

当 JSONPath 被用作安全机制的一部分时,攻击者 可以试图诱发意外或不可预测的行为,或 利用 JSONPath 实现之间的行为差异。

意外或不可预测的行为可能源自具有 某些由 [RFC8259] 描述为不可预测的构造的查询实参。 对于符合 [RFC7493] 的任何查询实参,除对象排序相关情况外, 可以预期有可预测的行为。

其他攻击可以针对底层技术的行为,例如 UTF-8(见 [RFC3629] 的 第 10 节) 和 Unicode 字符集。

5. 参考文献

5.1. 规范性参考文献

[RFC0020]
Cerf, V., “用于网络 交换的 ASCII 格式”, STD 80, RFC 20, DOI 10.17487/RFC0020, , <https://www.rfc-editor.org/info/rfc20>.
[RFC2119]
Bradner, S., “RFC 中用于 指示要求级别的关键词”, BCP 14, RFC 2119, DOI 10.17487/RFC2119, , <https://www.rfc-editor.org/info/rfc2119>.
[RFC3629]
Yergeau, F., “UTF-8,ISO 10646 的一种转换 格式”, STD 63, RFC 3629, DOI 10.17487/RFC3629, , <https://www.rfc-editor.org/info/rfc3629>.
[RFC5234]
Crocker, D., Ed.P. Overell, “用于语法规范的扩充 BNF:ABNF”, STD 68, RFC 5234, DOI 10.17487/RFC5234, , <https://www.rfc-editor.org/info/rfc5234>.
[RFC6838]
Freed, N., Klensin, J., 和 T. Hansen, “媒体类型规范与 注册程序”, BCP 13, RFC 6838, DOI 10.17487/RFC6838, , <https://www.rfc-editor.org/info/rfc6838>.
[RFC7493]
Bray, T., Ed., “I-JSON 消息 格式”, RFC 7493, DOI 10.17487/RFC7493, , <https://www.rfc-editor.org/info/rfc7493>.
[RFC8126]
Cotton, M., Leiba, B., 和 T. Narten, “RFC 中撰写 IANA 注意事项一节的指南”, BCP 26, RFC 8126, DOI 10.17487/RFC8126, , <https://www.rfc-editor.org/info/rfc8126>.
[RFC8174]
Leiba, B., “RFC 2119 关键词中大写与 小写的歧义”, BCP 14, RFC 8174, DOI 10.17487/RFC8174, , <https://www.rfc-editor.org/info/rfc8174>.
[RFC8259]
Bray, T., Ed., “JavaScript 对象 表示法(JSON)数据交换格式”, STD 90, RFC 8259, DOI 10.17487/RFC8259, , <https://www.rfc-editor.org/info/rfc8259>.
[RFC9485]
Bormann, C.T. Bray, “I-Regexp:一种可互操作的正则表达式格式”, RFC 9485, DOI 10.17487/RFC9485, , <https://www.rfc-editor.org/info/rfc9485>.
[UNICODE]
The Unicode Consortium, “Unicode® 标准”, <https://www.unicode.org/versions/latest/>. 撰写本文时,见 <https://www.unicode.org/versions/Unicode15.0.0/UnicodeStandard-15.0.pdf>

5.2. 资料性参考文献

[BOOLEAN-LAWS]
“布尔代数:定律”, , <https://en.wikipedia.org/w/index.php?title=Boolean_algebra&oldid=1191386550#Laws>.
[COMPARISON]
Burgmer, C., “JSONPath 比较”, <https://cburgmer.github.io/json-path-comparison/>.
[E4X]
ISO, “信息技术 - 用于 XML 的 ECMAScript (E4X)规范”, 已撤回, ISO/IEC 22537:2006, , <https://www.iso.org/standard/41002.html>. 一个同样已撤回的等价规范可从 <https://ecma-international.org/publications-and-standards/standards/ecma-357> 获取。
[ECMA-262]
ECMA International, “ECMAScript 语言 规范”, 标准 ECMA-262,第三版, , <https://www.ecma-international.org/wp-content/uploads/ECMA-262_3rd_edition_december_1999.pdf>.
[JSONPath-orig]
Gössner, S., “JSONPath - 面向 JSON 的 XPath”, , <https://goessner.net/articles/JsonPath/>.
[RFC6901]
Bryan, P., Ed., Zyp, K., 和 M. Nottingham, Ed., “JavaScript 对象 表示法(JSON)Pointer”, RFC 6901, DOI 10.17487/RFC6901, , <https://www.rfc-editor.org/info/rfc6901>.
[RFC8949]
Bormann, C.P. Hoffman, “简明二进制对象表示(CBOR)”, STD 94, RFC 8949, DOI 10.17487/RFC8949, , <https://www.rfc-editor.org/info/rfc8949>.
[SLICE]
“切片记法”, commit 82f95b4, , <https://github.com/tc39/proposal-slice-notation>.
[XPath]
Berglund, A., Ed., Chamberlin, D., Ed., Simeon, J., Ed., Robie, J., Ed., Fernandez, M., Ed., Kay, M., Ed., 和 S. Boag, Ed., “XML Path Language(XPath)2.0(第二版)”, W3C REC-xpath20-20101214, , <https://www.w3.org/TR/2010/REC-xpath20-20101214/>.

附录 A. 汇总的 ABNF 语法

本附录汇总了贯穿本文档所用 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)
图 2JSONPath 查询的汇总 ABNF

图 3 包含 定义 JSONPath 规范化路径语法的汇总 ABNF 语法,同时还使用 图 2 中的规则 root-identifierESCDIGITDIGIT1

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
图 3JSONPath 规范化路径的汇总 ABNF

附录 B. 受 XPath 启发

本附录为资料性内容。

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 语言被设计为:

B.1. JSONPath 和 XPath

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 概念的比较。

表 20XPath 语法与 JSONPath 的比较
XPath JSONPath 描述
/ $ 根 XML 元素
. @ 当前 XML 元素
/ .[] 子运算符
.. 不适用 父运算符
// ..name..⁠[index]..*..[*] 后代(JSONPath 从 E4X 借用了此 语法)
* * 通配符:所有 XML 元素,不论 其名称如何
@ 不适用 属性访问:JSON 值 没有属性
[] [] 下标运算符,用于迭代 XML 元素集合以及用于谓词
| [,] 联合运算符(结果为 节点集的组合);在 JSONPath 中称为列表运算符,允许组合 成员名、数组索引和切片
不适用 [start:end:step] 从 ES4 借用的数组切片运算符
[] ? 应用过滤器(脚本)表达式
无缝 不适用 表达式引擎
() 不适用 分组

为了进一步说明,表 21 展示了一些 XPath 表达式 及其 JSONPath 等价形式。

表 21XPath 表达式示例 及其 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 中下标运算符的工作方式存在显著差异:

  • XPath 表达式中的方括号始终作用于 前一路径片段所产生的节点集。 索引始终从 1 开始。
  • 使用 JSONPath 时,方括号作用于前一查询段所产生 节点列表中的每个节点。数组索引始终从 0 开始。

附录 C. JSON Pointer

本附录为资料性内容。

相对于 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 实现应用于众多查询时的行为。

贡献者

Marko Mikulicic
InfluxData, Inc.
Pisa
Italy
Edward Surov
TheSoul Publishing Ltd.
Limassol
Cyprus
Greg Dennis
Auckland
New Zealand

作者地址

Stefan Gössner(编辑
Fachhochschule Dortmund
Sonnenstraße 96
D-44139 Dortmund
Germany
Glyn Normington(编辑
Winchester
United Kingdom
Carsten Bormann(编辑
Universität Bremen TZI
Postfach 330440
D-28359 Bremen
Germany
电话: +49-421-218-63921