RedisJSON是Redis官方提供的一个模块,它让Redis拥有了原生存储和操作JSON数据的能力。与普通字符串不同,JSON数据在Redis内部以二进制格式保存,并且支持通过路径表达式直接访问内部字段,而不是把整个JSON取回客户端后再解析。要发挥RedisJSON的威力,路徑表达式就是必须掌握的核心语法。许多使用者一开始接触时,往往会看到类似[{"name":"test"}]这样带中括号和引号的路径,不明白它与普通键名的区别,更不清楚为什么命令返回的总是数组格式。接下来,我们就从最基础的路径语法开始,一步步拆解RedisJSON路径表达式。

RedisJSON路径表达式的基础结构
RedisJSON模块中的路径表达式遵循JSONPath的简化设计,但又做了针对性的裁剪。完整的路径表达式以美元符号$开头,表示JSON文档的根节点。与Redis传统键的扁平结构不同,JSON数据本身是分层的,所以路径中需要借助点号.来分隔对象层级的键名,或者借助中括号[]来指定数组的下标。这种设计让开发者可以用一种直观的方式定位到任意深度的子节点。
举个例子,假设我们在Redis中存储了一个包含用户信息的JSON对象,结构如下:
{
"username": "alice",
"profile": {
"age": 28,
"city": "Beijing"
},
"hobbies": ["reading", "swimming", "coding"]
}
如果用JSON.SET命令写入这段数据,我们可以这样操作:
127.0.0.1:6379> JSON.SET user:1 $ '{"username":"alice","profile":{"age":28,"city":"Beijing"},"hobbies":["reading","swimming","coding"]}'
OK
这里的$就代表了整个JSON文档。如果想读取username字段,路径可以写成$.username,结果会返回一个JSON数组,其中包含匹配到的值。为什么是数组呢?因为路径表达式可能匹配到多个结果,比如使用通配符时,返回值统一用数组形式呈现,便于客户端处理。如果只想获取根节点下的所有内容,可以直接使用$,效果与不写路径但使用JSON.GET命令的效果不同,后者会返回整个文档。
路径表达式中的点号与JavaScript访问对象属性的方式非常相似。访问嵌套对象中的字段时,使用多个点号即可,例如$.profile.city。访问数组元素时,使用下标索引,例如$.hobbies[0]代表第一个兴趣爱好。下标从0开始,这与绝大多数编程语言的数组一致。这种组合写法可以让开发者像操作本地对象一样操作Redis中的JSON数据。
常用命令中的路径表达式用法
理解了基础语法后,我们来看几个最常用的RedisJSON命令如何使用路径表达式。首先是JSON.GET,它接收一个key和至少一个路径参数。如果不传路径,默认返回整个文档;如果传入路径,则只返回对应位置的数据。这里有一个容易踩坑的点:当路径指向的值不存在时,JSON.GET会返回一个空数组,而不是错误。这一点与普通字符串操作不同,需要特别留意。
127.0.0.1:6379> JSON.GET user:1 $.profile.city "[\"Beijing\"]" 127.0.0.1:6379> JSON.GET user:1 $.hobbies[2] "[\"coding\"]" 127.0.0.1:6379> JSON.GET user:1 $.profile.country "[]"
从上面可以看出,每次查询的结果都被包裹在方括号中,这是因为RedisJSON把路径匹配结果统一包装成JSON数组返回。如果你只希望得到原始值而非数组,可以在服务端使用JSON.GET的FORMAT选项,或者直接在客户端处理返回结果。但更常见的做法是在查询时使用不带外层数组的路径吗?实际上,RedisJSON从较早版本就支持返回原值的能力,只是默认返回数组。理解这一机制有助于后续编写代码时避免类型判断错误。
JSON.SET命令也支持路径表达式,用于更新JSON中的特定字段。更新时路径必须指向一个已存在的值,否则命令会执行失败。这在修改嵌套数据时非常有用,我们不需要先取出整个JSON再放回去,而是直接指定路径进行更新。
127.0.0.1:6379> JSON.SET user:1 $.profile.age 29 OK 127.0.0.1:6379> JSON.GET user:1 $.profile.age "[29]"
JSON.TYPE命令可以查询路径所指向的数据类型,它返回的是JSON元素类型,比如object、array、string、integer等。这个命令对于调试路径是否正确很有帮助,因为如果路径写错,返回的是空数组,而空数组本身也是array类型,容易造成误判。需要结合值的内容来分析。
127.0.0.1:6379> JSON.TYPE user:1 $.profile "[\"object\"]" 127.0.0.1:6379> JSON.TYPE user:1 $.hobbies "[\"array\"]"
除这三个命令外,JSON.ARRAPPEND、JSON.OBJKEYS等命令同样接受路径表达式。共同点是所有路径都以$开始,并且可以精准定位到目标节点。熟练掌握这些命令与路径的搭配,是高效使用RedisJSON的关键。
进阶路径语法:通配符、递归与过滤器
实际业务中,JSON结构往往比示例复杂得多,嵌套层级深、数组长度不确定,这时候简单的点号和下标就有些不够用了。RedisJSON提供了通配符、递归下降以及过滤器等高级语法,帮助我们应对复杂场景。
通配符*可以匹配对象中的所有键,也可以匹配数组中的所有元素。例如$.profile.*会返回profile对象中所有字段的值,而$.hobbies[*]会返回hobbies数组中的所有元素。通配符常常用在只需要遍历全部子节点的场景,比如统计配置项的数量,或者批量提取某些属性。
127.0.0.1:6379> JSON.GET user:1 $.profile.* "[\"Beijing\",29]" 127.0.0.1:6379> JSON.GET user:1 $.hobbies[*] "[\"reading\",\"swimming\",\"coding\"]"
递归下降语法..可以深度遍历所有子节点。例如$.profile..*会递归取出profile下所有层级的值,包括嵌套对象中的字段。这在查询具有不可预知深度的树形结构时非常有效。但要注意,递归下降会遍历整个子树,当数据量较大时,性能开销不容忽视,建议在明确知道层级深度时优先使用精确路径。
过滤器语法[?(@.条件)]可以基于条件筛选数组中的元素。比如有一个包含多个商品对象的数组,想找出价格大于100的商品,路径可以写成$.goods[?(@.price > 100)]。这里需要特别说明,在Redis命令行中,大于号等符号需要转义或加引号,避免被shell解释。过滤器内部支持比较运算符、逻辑运算符以及正则匹配,灵活性很高。
127.0.0.1:6379> JSON.SET products:1 $ '{"goods":[{"name":"apple","price":80},{"name":"banana","price":120},{"name":"pear","price":90}]}'
OK
127.0.0.1:6379> JSON.GET products:1 '$.goods[?(@.price > 100)]'
"[{\"name\":\"banana\",\"price\":120}]"
除了基础过滤器,还支持使用@.字段名访问当前元素,也可以嵌套使用多个条件。需要注意的是,RedisJSON的过滤器与标准JSONPath并不完全一致,某些表达式可能不支持。官方文档给出了一个明确的语法支持列表,实际开发时应当以当前Redis版本为准。同时,过滤器内的字符串值必须使用双引号包裹,这与JSON标准一致,在命令行中还需要对外层的单引号进行区分。
对于包含特殊字符的键名,比如包含点号、空格或中文的键,路径表达式需要使用双引号将键名括起来。例如{"user name":"alice"},访问路径应写成$["user name"]。如果需要访问的键名中包含双引号本身,则要使用反斜杠进行转义。这种写法虽然略显繁琐,但能确保路径被正确解析。
路径表达式常见误区与性能建议
很多使用者在编写路径时,容易忘记以$开头,直接把JSON中的键名作为路径,例如直接写profile.age。这是错误的,因为RedisJSON要求所有路径表达式必须以根节点符号$开头,否则命令会返回语法错误。另外,也有不少人混淆了点号与中括号的使用场景。点号只能用于对象键的访问,中括号既可以用于数组索引,也可以用于对象键的引用,比如$["profile"]["age"]与$.profile.age是等价的。掌握这种等价关系有助于理解不同写法的本质。
在性能方面,路径表达式的解析和遍历会消耗一定的CPU资源。对于高并发的读取场景,建议尽量使用精确路径,避免使用通配符和递归下降,因为它们需要遍历更多节点。例如$.profile.city比$.profile.*精准得多,响应时间也更稳定。另外,过滤器的执行成本通常高于普通路径,因为RedisJSON需要为每个元素评估条件表达式。如果数据量较大,可以考虑在应用层预处理数据,或者使用Redis的索引模块来辅助查询。
还有一点值得注意:路径表达式与Redis的过期时间、内存淘汰策略无关,它们只影响JSON数据的访问方式。当使用JSON.DEL命令删除路径时,如果路径匹配到多个值,所有匹配的值都会被删除,因此需要谨慎使用通配符删除操作。一个安全的做法是先使用JSON.GET测试路径,确认匹配范围后再执行删除。
最后,再来看一个结合了递归与过滤器的综合示例。假设有一个公司部门结构,包含若干员工,想查找所有部门中工资大于5000的员工姓名,路径可以这样写:
127.0.0.1:6379> JSON.SET company:1 $ '{"org":{"departments":[{"name":"dev","employees":[{"name":"Bob","salary":6000},{"name":"Alice","salary":4500}]},{"name":"ops","employees":[{"name":"Tom","salary":7000}]}]}}'
OK
127.0.0.1:6379> JSON.GET company:1 '$.org.departments[*].employees[?(@.salary > 5000)].name'
"[\"Bob\",\"Tom\"]"
这个路径首先通过通配符[*]遍历所有部门,然后使用过滤器筛选工资大于5000的员工,最后取出name字段。整个过程清晰地展示了组合使用进阶语法带来的表达能力。但也要明白,这样的查询虽然强大,却需要RedisJSON在内存中遍历多个数组元素,当部门数量和员工数量很大时,性能会明显下降。因此,这类复杂路径更适合用于离线分析或低频管理操作,而不适合放在用户请求的热路径上。
综上所述,RedisJSON路径表达式是一种既直观又灵活的JSON访问方式。从基础的美元符号根路径,到数组下标、通配符、递归下降和过滤器,每个语法都有其特定的适用场景。只要掌握了这些规则,并且留意命令返回参数与性能差异,就能在项目中安全高效地使用RedisJSON。如果你的数据模型具有清晰的层次和相对稳定的结构,建议优先使用点号路径;如果遇到复杂的动态结构,再考虑使用高级语法。理解路径表达式的本质,比死记硬背几条命令更加重要。