# PATH

访问 JSON 文档中的特定元素

路径可帮助您访问 JSON 文档中的特定元素。由于不存在 JSON 路径语法的标准,因此 RedisJSON 实现了自己的标准。RedisJSON 的语法基于常见的最佳实践,并且有意类似于 JSONPath

RedisJSON 支持两种查询语法: JSONPath 语法 和 RedisJSON 第一个版本的 旧路径语法

RedisJSON 根据路径查询的第一个字符知道使用哪种语法。如果查询以字符 开头$,则它使用 JSONPath 语法。否则,它默认为旧路径语法。

# JSONPath 支持

RedisJSON v2.0 引入了 JSONPath 支持。它遵循 Goessner 在他的文章中描述的语法。

JSONPath 查询可以解析到 JSON 文档中的多个位置。在这种情况下,JSON 命令将操作应用于每个可能的位置。这是对仅在第一条路径上运行的 遗留路径 查询的重大改进。

请注意,使用 JSONPath 时,命令响应的结构通常会有所不同。有关更多详细信息,请参阅 命令 页面。

新语法支持括号表示法,允许在键名中使用冒号“:”或空格等特殊字符。

如果要在查询中包含双引号,请将 JSONPath 括在单引号中。例如:

JSON.GET store '$.inventory["headphones"]'

# JSONPath 语法

以下 JSONPath 语法表改编自 Goessner 的 路径语法比较

语法元素 描述
$ 根(最外层的 JSON 元素)开始路径。
. 或者 [] 选择一个子元素。
.. 递归地遍历 JSON 文档。
* 通配符,返回所有元素。
[] 下标运算符,访问数组元素。
[,] 联合,选择多个元素。
[开始:结束:步骤] 数组切片,其中 start、end 和 step 是索引。
?() 过滤 JSON 对象或数组。支持比较运算符(==、!=、<、<=、>、>=)和逻辑运算符(&&、||)。
() 脚本表达式。
@ 当前元素,用于过滤器或脚本表达式。

# JSONPath 示例

以下 JSONPath 示例使用此 JSON 文档,该文档存储有关商店库存中商品的详细信息:

{
   "inventory": {
      "headphones": [
         {
            "id": 12345,
            "name": "Noise-cancelling Bluetooth headphones",
            "description": "Wireless Bluetooth headphones with noise-cancelling technology",
            "wireless": true,
            "connection": "Bluetooth",
            "price": 99.98,
            "stock": 25,
            "free-shipping": false,
            "colors": ["black", "silver"]
         },
         {
            "id": 12346,
            "name": "Wireless earbuds",
            "description": "Wireless Bluetooth in-ear headphones",
            "wireless": true,
            "connection": "Bluetooth",
            "price": 64.99,
            "stock": 17,
            "free-shipping": false,
            "colors": ["black", "white"]
         },
         {
            "id": 12347,
            "name": "Mic headset",
            "description": "Headset with built-in microphone",
            "wireless": false,
            "connection": "USB",
            "price": 35.01,
            "stock": 28,
            "free-shipping": false
         }
      ],
      "keyboards": [
         {
            "id": 22345,
            "name": "Wireless keyboard",
            "description": "Wireless Bluetooth keyboard",
            "wireless": true,
            "connection": "Bluetooth",
            "price": 44.99,
            "stock": 23,
            "free-shipping": false,
            "colors": ["black", "silver"]
         },
         {
            "id": 22346,
            "name": "USB-C keyboard",
            "description": "Wired USB-C keyboard",
            "wireless": false,
            "connection": "USB-C",
            "price": 29.99,
            "stock": 30,
            "free-shipping": false
         }
      ]
   }
}

首先,在您的数据库中创建 JSON 文档:

JSON.SET store $ '{"inventory":{"headphones":[{"id":12345,"name":"Noise-cancelling Bluetooth headphones","description":"Wireless Bluetooth headphones with noise-cancelling technology","wireless":true,"connection":"Bluetooth","price":99.98,"stock":25,"free-shipping":false,"colors":["black","silver"]},{"id":12346,"name":"Wireless earbuds","description":"Wireless Bluetooth in-ear headphones","wireless":true,"connection":"Bluetooth","price":64.99,"stock":17,"free-shipping":false,"colors":["black","white"]},{"id":12347,"name":"Mic headset","description":"Headset with built-in microphone","wireless":false,"connection":"USB","price":35.01,"stock":28,"free-shipping":false}],"keyboards":[{"id":22345,"name":"Wireless keyboard","description":"Wireless Bluetooth keyboard","wireless":true,"connection":"Bluetooth","price":44.99,"stock":23,"free-shipping":false,"colors":["black","silver"]},{"id":22346,"name":"USB-C keyboard","description":"Wired USB-C keyboard","wireless":false,"connection":"USB-C","price":29.99,"stock":30,"free-shipping":false}]}}'

# 访问 JSON 示例

以下示例使用该 JSON.GET 命令从 JSON 文档中的各种路径检索数据。

您可以使用通配符运算符*返回库存中所有项目的列表:

127.0.0.1:6379> JSON.GET store $.inventory.*
"[[{\"id\":12345,\"name\":\"Noise-cancelling Bluetooth headphones\",\"description\":\"Wireless Bluetooth headphones with noise-cancelling technology\",\"wireless\":true,\"connection\":\"Bluetooth\",\"price\":99.98,\"stock\":25,\"free-shipping\":false,\"colors\":[\"black\",\"silver\"]},{\"id\":12346,\"name\":\"Wireless earbuds\",\"description\":\"Wireless Bluetooth in-ear headphones\",\"wireless\":true,\"connection\":\"Bluetooth\",\"price\":64.99,\"stock\":17,\"free-shipping\":false,\"colors\":[\"black\",\"white\"]},{\"id\":12347,\"name\":\"Mic headset\",\"description\":\"Headset with built-in microphone\",\"wireless\":false,\"connection\":\"USB\",\"price\":35.01,\"stock\":28,\"free-shipping\":false}],[{\"id\":22345,\"name\":\"Wireless keyboard\",\"description\":\"Wireless Bluetooth keyboard\",\"wireless\":true,\"connection\":\"Bluetooth\",\"price\":44.99,\"stock\":23,\"free-shipping\":false,\"colors\":[\"black\",\"silver\"]},{\"id\":22346,\"name\":\"USB-C keyboard\",\"description\":\"Wired USB-C keyboard\",\"wireless\":false,\"connection\":\"USB-C\",\"price\":29.99,\"stock\":30,\"free-shipping\":false}]]"

对于某些查询,多个路径可以产生相同的结果。例如,以下路径返回所有耳机的名称:

127.0.0.1:6379> JSON.GET store $.inventory.headphones[*].name
"[\"Noise-cancelling Bluetooth headphones\",\"Wireless earbuds\",\"Mic headset\"]"
127.0.0.1:6379> JSON.GET store '$.inventory["headphones"][*].name'
"[\"Noise-cancelling Bluetooth headphones\",\"Wireless earbuds\",\"Mic headset\"]"
127.0.0.1:6379> JSON.GET store $..headphones[*].name
"[\"Noise-cancelling Bluetooth headphones\",\"Wireless earbuds\",\"Mic headset\"]"

递归下降运算符..可以从 JSON 文档的多个部分检索字段。以下示例返回所有库存项目的名称:

127.0.0.1:6379> JSON.GET store $..name
"[\"Noise-cancelling Bluetooth headphones\",\"Wireless earbuds\",\"Mic headset\",\"Wireless keyboard\",\"USB-C keyboard\"]"

您可以使用数组切片从数组中选择一系列元素。此示例返回前两个耳机的名称:

127.0.0.1:6379> JSON.GET store $..headphones[0:2].name
"[\"Noise-cancelling Bluetooth headphones\",\"Wireless earbuds\"]"

过滤器表达式?()允许您根据特定条件选择 JSON 元素。您可以在这些表达式中使用比较运算符(==、!=、<、<=、>、>=)和逻辑运算符(&&、||)。

例如,这个过滤器只返回价格低于 70 的无线耳机:

127.0.0.1:6379> JSON.GET store $..headphones[?(@.price<70&&@.wireless==true)]
"[{\"id\":12346,\"name\":\"Wireless earbuds\",\"description\":\"Wireless Bluetooth in-ear headphones\",\"wireless\":true,\"connection\":\"Bluetooth\",\"price\":64.99,\"stock\":17,\"free-shipping\":false,\"colors\":[\"black\",\"white\"]}]"

此示例筛选支持蓝牙连接的项目名称的清单:

127.0.0.1:6379> JSON.GET store '$.inventory.*[?(@.connection=="Bluetooth")].name'
"[\"Noise-cancelling Bluetooth headphones\",\"Wireless earbuds\",\"Wireless keyboard\"]"

# 更新 JSON 示例

当您想要更新 JSON 文档的特定部分时,还可以使用 JSONPath 查询。

例如,您可以将 JSONPath 传递给 JSON.SET 命令以更新特定字段。此示例更改耳机列表中第一项的价格:

127.0.0.1:6379> JSON.GET store $..headphones[0].price
"[99.98]"
127.0.0.1:6379> JSON.SET store $..headphones[0].price 78.99
"OK"
127.0.0.1:6379> JSON.GET store $..headphones[0].price
"[78.99]"

您可以使用过滤器表达式仅更新与特定条件匹配的 JSON 元素。对于价格大于 49 的任何商品,以下示例更改free-shipping为:true

127.0.0.1:6379> JSON.SET store $.inventory.*[?(@.price>49)].free-shipping true
"OK"
127.0.0.1:6379> JSON.GET store $.inventory.*[?(@.free-shipping==true)].name
"[\"Noise-cancelling Bluetooth headphones\",\"Wireless earbuds\"]"

JSONPath 查询也适用于接受路径作为参数的其他 JSON 命令。例如,您可以为一组耳机添加新的颜色选项 JSON.ARRAPPEND

127.0.0.1:6379> JSON.GET store $..headphones[0].colors
"[[\"black\",\"silver\"]]"
127.0.0.1:6379> JSON.ARRAPPEND store $..headphones[0].colors '"pink"'
1) "3"
127.0.0.1:6379> JSON.GET store $..headphones[0].colors
"[[\"black\",\"silver\",\"pink\"]]"

# 旧路径语法

RedisJSON v1 具有以下路径实现。除了 JSONPath 之外,RedisJSON v2 仍然支持这个遗留路径。

路径总是从 RedisJSON 值的根开始。根由句点字符 ( .) 表示。对于引用根的孩子的路径,可以选择在路径前加上根。

RedisJSON 支持对象键访问的点表示法和括号表示法。以下路径引用了耳机,它是根目录下的清单的子项:

  • .inventory.headphones
  • inventory["headphones"]
  • ['inventory']["headphones"]

要访问数组元素,请将其索引括在一对方括号内。索引是从 0 开始的,0 是数组的第一个元素,1 是下一个元素,依此类推。您可以使用负偏移量来访问从数组末尾开始的元素。例如,-1 是数组中的最后一个元素,-2 是倒数第二个元素,依此类推。

# JSON 键名和路径兼容性

根据定义,JSON 键可以是任何有效的 JSON 字符串。另一方面,路径传统上基于 JavaScript(和 Java)的变量命名约定。

尽管 RedisJSON 可以存储包含任意键名的对象,但如果它们符合以下命名语法规则,则只能使用旧路径访问这些键:

  1. 名称必须以字母、美元符号 ( $) 或下划线 ( _) 字符开头
  2. 名称可以包含字母、数字、美元符号和下划线
  3. 名称区分大小写

# 路径评估的时间复杂度

在路径中搜索(导航到)元素的时间复杂度计算如下:

  1. 子级别 - 路径上的每个级别都会添加额外的搜索
  2. 键搜索 - O(N) †,其中 N 是父对象中的键数
  3. 数组搜索 - O(1)

这意味着搜索路径的总体时间复杂度为O(N*M),其中 N 是深度,M 是父对象键的数量。

†虽然这对于 N 较小的对象是可以接受的,但可以针对较大的对象优化访问。

Last Updated: 4/18/2023, 8:45:33 AM