DiceDB JSON.ARRAPPEND 命令详解:向 JSON 数组尾部追加元素
【免费下载链接】dicedbOpen-source, low-latency key/value engine built on Valkey with query subscriptions and hierarchical storage tiers.项目地址: https://gitcode.com/GitHub_Trending/dic/dicedb
JSON.ARRAPPEND是 DiceDB 中 DiceDBJSON 模块提供的原生 JSON 命令之一,用于向指定 JSON 文档中某个路径所指向的数组末尾追加一个或多个 JSON 值,并返回追加后数组的新长度。本文以仓库中的官方命令文档 JSON.ARRAPPEND.md 为核心骨架,结合 DiceDB 的命令注册、求值器实现与测试用例,完整讲解其语法、参数、返回值、错误语义与底层执行原理,读者可以据此在 DiceDB 中正确、高效地完成 JSON 数组的动态扩展操作。
命令概述
DiceDB 是一个开源、低延迟的键值引擎(low-latency key/value engine),通过 DiceDBJSON 模块提供原生的 JSON 数据能力。JSON.ARRAPPEND正是这套 JSON 命令体系中的数组追加原语,与JSON.SET、JSON.GET、JSON.ARRLEN、JSON.ARRINSERT、JSON.ARRPOP等命令配合,可以在不读取、反序列化整个文档的前提下,对嵌套 JSON 数组进行就地修改。
从命令注册表看,该命令被标记为已迁移(IsMigrated: true),其元数据定义在 internal/eval/commands.go:
jsonarrappendCmdMeta = DiceCmdMeta{ Name: "JSON.ARRAPPEND", Info: `JSON.ARRAPPEND key [path] value [value ...] Returns an array of integer replies for each path, the array's new size, or nil, if the matching JSON value is not an array.`, Arity: -3, IsMigrated: true, NewEval: evalJSONARRAPPEND, }Arity: -3表示该命令至少需要 3 个参数(key、path、至少一个 value),且参数个数可变;实际执行逻辑由evalJSONARRAPPEND函数承担(见下文"源码级实现剖析")。
语法
JSON.ARRAPPEND <key> <path> <json_value> [<json_value> ...]<key>:必填,要操作的键。<path>:必填,指向 JSON 文档中数组位置的 JSONPath 表达式。<json_value>:必填,一个或多个要追加的 JSON 值,可重复传入多个。
参数说明
| 参数 | 类型 | 说明 |
|---|---|---|
key | String | 存储 JSON 文档的键名。 |
path | String | JSONPath 表达式,用于定位 JSON 文档中数组所在的位置。 |
json_value | JSON | 一个或多个要追加到数组末尾的 JSON 值。这些值必须是合法的 JSON 数据类型,例如字符串、数字、对象、数组、布尔值或null。 |
需要特别强调的是,json_value是按JSON 字面量解析的:追加普通字符串时必须带引号(如"cherry"),追加对象/数组时使用{...}/[...]字面量。解析工作由底层实现调用sonic.UnmarshalString完成(见 internal/eval/store_eval.go),因此任何非法 JSON 输入都会直接报错。
返回值
Integer:追加操作完成后数组的新长度。- 当使用递归 JSONPath(如
$..)一次匹配多个数组时,返回由各数组新长度组成的整数数组;其中被匹配到但不是数组的节点,对应位置返回nil(该行为在官方文档的"Return Value"一节未展开,但已被源码与测试明确证实,详见下文)。
行为语义
JSON.ARRAPPEND执行时,会将指定的 JSON 值依次追加到key下 JSON 文档中path所指向数组的末尾。若路径不存在或路径指向的值不是数组,命令会报错;若键不存在,命令报错(详见下文错误处理,其中文档描述与当前源码实现存在一处细微差异,已在实现剖析中说明)。
该命令具备以下关键语义:
- 就地修改:数组元素是追加到目标数组的尾部,原有元素顺序与内容保持不变。
- 支持一次追加多个值:多个
json_value按命令行给定的先后顺序依次入列。 - 支持多路径匹配:当 JSONPath 使用递归下降语法(如
$..score)时,会对所有匹配到的数组执行追加。
错误处理
官方文档列出了如下错误场景,结合源码(internal/errors/errors.go)可确认其错误码来源:
| 错误场景 | 错误消息 | 触发条件 |
|---|---|---|
| 类型错误(Wrong type of value or key) | (error) WRONGTYPE Operation against a key holding the wrong kind of value | 键中存储的不是 JSON 类型值。源码中通过object.AssertType(obj.Type, object.ObjTypeJSON)校验,失败时返回ErrWrongTypeOperation(internal/errors/errors.go)。 |
| 键不存在(Invalid Key) | (error) ERR key does not exist | 对不存在的键执行追加。 |
| 路径不存在(Invalid Path) | (error) ERR path %s does not exist | JSON 文档中不存在path指定的位置,对应ErrJSONPathNotFound(internal/errors/errors.go)。 |
| 路径处不是数组(Non Array Value at Path) | (error) ERR path is not an array | path指向的值不是数组。 |
| 非法的 JSON 值(Invalid JSON) | (error) ERR invalid JSON | 传入的json_value不是合法的 JSON 字面量。 |
此外还有一类未在文档中单独列出、但由Arity: -3决定的错误:当参数个数少于 3 个(即缺少 value)时,返回ErrWrongArgumentCount("JSON.ARRAPPEND")错误(internal/errors/errors.go)。
示例用法
以下示例均基于 DiceDB 默认端口 7379 的命令行客户端(与官方文档一致)。
向数组追加单个值
127.0.0.1:7379> JSON.SET myjson . '{"numbers": [1, 2, 3]}' OK 127.0.0.1:7379> JSON.ARRAPPEND myjson .numbers 4 (integer) 4 127.0.0.1:7379> JSON.GET myjson "{\"numbers\":[1,2,3,4]}"向数组追加多个值
127.0.0.1:7379> JSON.SET myjson . '{"fruits": ["apple", "banana"]}' OK 127.0.0.1:7379> JSON.ARRAPPEND myjson .fruits "cherry" "date" (integer) 4 127.0.0.1:7379> JSON.GET myjson "{\"fruits\":[\"apple\",\"banana\",\"cherry\",\"date\"]}"键不存在时报错
127.0.0.1:7379> JSON.ARRAPPEND nonexistingkey .array 1 (error) ERR key does not exist路径不存在时报错
127.0.0.1:7379> JSON.SET myjson . '{"numbers": [1, 2, 3]}' OK 127.0.0.1:7379> JSON.ARRAPPEND myjson .nonexistingpath 4 (error) ERR path .nonexistingpath does not exist路径不是数组时报错
127.0.0.1:7379> JSON.SET myjson . '{"object": {"key": "value"}}' OK 127.0.0.1:7379> JSON.ARRAPPEND myjson .object 4 (error) ERR path is not an array传入非法 JSON 时报错
127.0.0.1:7379> JSON.SET myjson . '{"numbers": [1, 2, 3]}' OK 127.0.0.1:7379> JSON.ARRAPPEND myjson .numbers invalidjson (error) ERR invalid JSON进阶:在根路径处追加(根文档本身就是数组)
DiceDB 的 JSON 文档允许根节点即为数组,此时可用$作为路径直接在根数组上追加(该用法已被单元测试覆盖,见 internal/eval/eval_test.go):
127.0.0.1:7379> JSON.SET arr $ '[1,2,3]' OK 127.0.0.1:7379> JSON.ARRAPPEND arr $ 6 (integer) 4进阶:递归路径一次追加多个数组
使用$..score这类递归 JSONPath 时,命令会命中所有匹配的数组并分别追加,返回各数组的新长度:
127.0.0.1:7379> JSON.SET doc $ '{"partner":{"name":"tom","score":[10]},"partner2":{"score":[10,20]}}' OK 127.0.0.1:7379> JSON.ARRAPPEND doc $..score 10 1) (integer) 2 2) (integer) 3进阶:追加对象与数组等复合值
json_value支持任意合法 JSON,包括对象和数组字面量:
127.0.0.1:7379> JSON.SET doc $ '{"a":[{"b":1}]}' OK 127.0.0.1:7379> JSON.ARRAPPEND doc $.a '{"c":3}' (integer) 2 127.0.0.1:7379> JSON.SET arr $ '{"a":[[1,2]]}' OK 127.0.0.1:7379> JSON.ARRAPPEND arr $.a '[1,2,3]' (integer) 2源码级实现剖析
JSON.ARRAPPEND的完整执行逻辑位于 internal/eval/store_eval.go 的evalJSONARRAPPEND函数,其处理流程可拆解为以下五个阶段:
参数校验:要求
len(args) >= 3,即 key、path、至少一个 value,否则返回ErrWrongArgumentCount。取键与类型校验:通过
store.Get(key)获取对象;对象不存在(或已过期)时返回NIL结果。随后调用object.AssertType(obj.Type, object.ObjTypeJSON)校验对象确为 JSON 类型,否则返回WRONGTYPE错误。JSONPath 解析:调用
jp.ParseString(path)将路径字符串解析为 JSONPath 表达式,解析失败返回ErrJSONPathNotFound(path)。值解析:对每个
json_value调用sonic.UnmarshalString(v, &parsedValue)解析为 JSON 值,任何解析失败都会以通用错误形式返回——这就是"非法 JSON"错误的来源。就地修改:核心通过
expr.Modify(jsonData, callback)完成:回调函数检查当前节点是否为[]interface{}数组,是则执行arr = append(arr, parsedValues...)并记录新长度到resultsArray;不是数组则在该位置记入NIL。Modify返回后,若全程没有任何数组被修改(即路径完全不存在或没有匹配到数组),函数返回ErrJSONPathNotFound(path);否则将修改后的数据写回obj.Value并返回resultsArray。
这一实现揭示了一个值得注意的细节:当前源码中,当 key 不存在或已过期时,命令返回的是(nil)(Result: NIL, Error: nil),而不是文档中描述的ERR key does not exist错误。同时,递归路径下"部分匹配成功、部分不是数组"的场景会得到[长度, (nil), ...]的混合结果,而不是整体报错。这与 tests0/json_test.go 中的TestJsonARRAPPEND用例(如"JSON.ARRAPPEND nested with nil"期望返回[int64(2), "(nil)"])完全吻合,实际使用时请以线上实例的返回行为为准。
测试验证
DiceDB 为该命令提供了两层测试覆盖:
- 单元测试:internal/eval/eval_test.go 直接构造
evalJSONARRAPPEND的输入输出,覆盖追加嵌套数组值($.a追加[1,2,3])、追加对象值({"c":3})、递归多字段追加($..a返回[2,3])、根节点追加($)以及向数组追加不同类型值等场景,并断言 RESP 编码输出(如*2\r\n:2\r\n:3\r\n)。 - 端到端测试:tests0/json_test.go 通过真实客户端连接执行命令序列,覆盖根路径追加、递归追加(
$..score)、递归命中非数组节点(返回nil)以及混合数据类型等用例。
这些测试不仅验证了命令的正确性,也为我们理解返回值的各种形态(单个整数、整数数组、含nil的混合数组)提供了最直接的依据。
注意事项与最佳实践
- 确保 DiceDBJSON 模块已加载:使用
JSON.ARRAPPEND前,需要确认实例已启用 DiceDBJSON 模块提供的 JSON 能力。 - 熟悉 JSONPath 语法:
path使用 JSONPath 表达式定位文档位置。掌握.child、$.root、$..recursive等基础语法,可以更精准地控制追加目标;递归表达式会一次命中多个数组并返回多值结果。 - 值为 JSON 字面量:追加字符串必须加引号(
"cherry"),追加数字、布尔值、null、对象、数组时按 JSON 语法书写,否则会触发"invalid JSON"错误。 - 关注返回语义:单路径场景返回追加后数组的新长度,可直接用于判断操作是否生效;递归路径场景返回长度数组,其中
nil表示对应节点不是数组。 - 结合其他 JSON 命令使用:
JSON.ARRAPPEND通常与JSON.SET(初始化文档)、JSON.GET(查看结果)、JSON.ARRLEN(查询长度)、JSON.ARRPOP/JSON.ARRTRIM(数组收缩)配合,构成完整的数组生命周期管理。
通过本文档,读者可以全面掌握JSON.ARRAPPEND的语法、语义与实现细节,进而在 DiceDB 中自如地维护 JSON 数组数据。
【免费下载链接】dicedbOpen-source, low-latency key/value engine built on Valkey with query subscriptions and hierarchical storage tiers.项目地址: https://gitcode.com/GitHub_Trending/dic/dicedb
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考