From 21ec8f829b053562bdd5a909e297e51b500ff90d Mon Sep 17 00:00:00 2001 From: Andy Stark Date: Tue, 14 Jul 2026 13:37:00 +0100 Subject: [PATCH 1/2] DOC-6842 Add redis-rb (Ruby) JSON docs: data-type example + JSON.* command mappings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Preemptive, parked docs for redis-rb's JSON module (merged upstream in redis-rb #1346-#1349 but not yet in a released gem — the latest is v5.4.1 from July 2025, which predates the module). Adds a Ruby port of the json_tutorial doctest set and redis_rb signatures across the 22 JSON.* command-api-mapping files. Foundation for the redis-rb Query Engine docs, which index JSON documents and will build on this. Two non-obvious things for whoever edits these next. First, data/examples.json is deliberately absent from this change: it is git-ignored and generated, and the new Ruby tab on the JSON data-type page comes entirely from the dt_json.rb source via build/local_examples.py (.rb maps to ruby maps to the "Ruby" tab). Do not hand-add a Ruby entry there. Second, the example's expected outputs and REMOVE-block asserts are not guesses — the file was run against the local redis-rb fork (master) on Redis 8.8 and passed all 18 steps, so the only remaining risk is the upstream API changing before the gem ships. Learned: examples.json is generated (Ruby tab derives from the .rb source); example outputs were run-verified against the redis-rb fork, not guessed Constraint: regenerate data/command-api-mapping.json with build/merge-command-api-mapping.sh after editing per-command files — never hand-edit the merged file Directive: parked — do not merge until redis-rb ships the JSON module in a released gem Recheck: next redis-rb gem release (currently unreleased; JSON module is master-only) Gaps: verified against fork master on Redis 8.8, not a released gem — re-run the example at pickup Ticket: DOC-6842 Co-Authored-By: Claude Opus 4.8 (1M context) --- data/command-api-mapping.json | 582 ++++++++++++++++++ data/command-api-mapping/JSON.ARRAPPEND.json | 31 + data/command-api-mapping/JSON.ARRINDEX.json | 41 ++ data/command-api-mapping/JSON.ARRINSERT.json | 36 ++ data/command-api-mapping/JSON.ARRLEN.json | 21 + data/command-api-mapping/JSON.ARRPOP.json | 31 + data/command-api-mapping/JSON.ARRTRIM.json | 31 + data/command-api-mapping/JSON.CLEAR.json | 21 + .../JSON.DEBUG MEMORY.json | 21 + data/command-api-mapping/JSON.DEL.json | 21 + data/command-api-mapping/JSON.FORGET.json | 21 + data/command-api-mapping/JSON.GET.json | 26 + data/command-api-mapping/JSON.MERGE.json | 31 + data/command-api-mapping/JSON.MGET.json | 26 + data/command-api-mapping/JSON.MSET.json | 21 + data/command-api-mapping/JSON.NUMINCRBY.json | 26 + data/command-api-mapping/JSON.OBJKEYS.json | 21 + data/command-api-mapping/JSON.OBJLEN.json | 21 + data/command-api-mapping/JSON.SET.json | 41 ++ data/command-api-mapping/JSON.STRAPPEND.json | 31 + data/command-api-mapping/JSON.STRLEN.json | 21 + data/command-api-mapping/JSON.TOGGLE.json | 21 + data/command-api-mapping/JSON.TYPE.json | 21 + local_examples/tmp/datatypes/json/dt_json.rb | 367 +++++++++++ 24 files changed, 1531 insertions(+) create mode 100644 local_examples/tmp/datatypes/json/dt_json.rb diff --git a/data/command-api-mapping.json b/data/command-api-mapping.json index 292de0e487..0952456649 100644 --- a/data/command-api-mapping.json +++ b/data/command-api-mapping.json @@ -66988,6 +66988,37 @@ "description": "The array length after the append operation." } } + ], + "redis_rb": [ + { + "signature": "json_arrappend(key, path, *values, raw: false)", + "params": [ + { + "name": "key", + "type": "String", + "description": "" + }, + { + "name": "path", + "type": "String", + "description": "a JSONPath to the target array" + }, + { + "name": "*values", + "type": "Array", + "description": "one or more JSON values to append" + }, + { + "name": "raw", + "type": "Boolean", + "description": "treat each value as an already-encoded JSON string" + } + ], + "returns": { + "type": "Array, Integer", + "description": "the new array length(s); an Array for a JSONPath, a single Integer for a legacy path (nil for a match that is not an array)" + } + } ] } }, @@ -67656,6 +67687,47 @@ "description": "The index of the first matching element in the requested range." } } + ], + "redis_rb": [ + { + "signature": "json_arrindex(key, path, value, start: nil, stop: nil, raw: false)", + "params": [ + { + "name": "key", + "type": "String", + "description": "" + }, + { + "name": "path", + "type": "String", + "description": "a JSONPath to the target array" + }, + { + "name": "value", + "type": "Object", + "description": "the scalar JSON value to search for" + }, + { + "name": "start", + "type": "Integer", + "description": "optional inclusive start index" + }, + { + "name": "stop", + "type": "Integer", + "description": "optional exclusive stop index" + }, + { + "name": "raw", + "type": "Boolean", + "description": "treat value as an already-encoded JSON string" + } + ], + "returns": { + "type": "Array, Integer", + "description": "the index/indices of the first match (-1 if not found)" + } + } ] } }, @@ -68127,6 +68199,42 @@ "description": "The array length after the insert operation." } } + ], + "redis_rb": [ + { + "signature": "json_arrinsert(key, path, index, *values, raw: false)", + "params": [ + { + "name": "key", + "type": "String", + "description": "" + }, + { + "name": "path", + "type": "String", + "description": "a JSONPath to the target array" + }, + { + "name": "index", + "type": "Integer", + "description": "the position to insert before" + }, + { + "name": "*values", + "type": "Array", + "description": "one or more JSON values to insert" + }, + { + "name": "raw", + "type": "Boolean", + "description": "treat each value as an already-encoded JSON string" + } + ], + "returns": { + "type": "Array, Integer", + "description": "the new array length(s)" + } + } ] } }, @@ -68442,6 +68550,27 @@ "description": "The length of the JSON array at the requested path." } } + ], + "redis_rb": [ + { + "signature": "json_arrlen(key, path = nil)", + "params": [ + { + "name": "key", + "type": "String", + "description": "" + }, + { + "name": "path", + "type": "String", + "description": "an optional JSONPath (defaults to the root \"$\")" + } + ], + "returns": { + "type": "Array, Integer, nil", + "description": "the array length(s); nil for a match that is not an array, or when the key/path does not exist" + } + } ] } }, @@ -68907,6 +69036,37 @@ "description": "The removed JSON element." } } + ], + "redis_rb": [ + { + "signature": "json_arrpop(key, path = nil, index = nil, raw: false)", + "params": [ + { + "name": "key", + "type": "String", + "description": "" + }, + { + "name": "path", + "type": "String", + "description": "an optional JSONPath to the target array (defaults to the root \"$\")" + }, + { + "name": "index", + "type": "Integer", + "description": "an optional position to pop from (defaults to -1, the last element)" + }, + { + "name": "raw", + "type": "Boolean", + "description": "return the unparsed JSON string(s) instead of parsed Ruby objects" + } + ], + "returns": { + "type": "Array, Object, nil", + "description": "the popped value(s); an Array for a JSONPath, a single value for a legacy path, nil for an empty array or a non-array match" + } + } ] } }, @@ -69261,6 +69421,37 @@ "description": "The length of the array after trimming." } } + ], + "redis_rb": [ + { + "signature": "json_arrtrim(key, path, start, stop)", + "params": [ + { + "name": "key", + "type": "String", + "description": "" + }, + { + "name": "path", + "type": "String", + "description": "a JSONPath to the target array" + }, + { + "name": "start", + "type": "Integer", + "description": "inclusive index of the first element to keep" + }, + { + "name": "stop", + "type": "Integer", + "description": "inclusive index of the last element to keep" + } + ], + "returns": { + "type": "Array, Integer", + "description": "the new array length(s)" + } + } ] } }, @@ -69590,6 +69781,27 @@ "description": "The number of values cleared." } } + ], + "redis_rb": [ + { + "signature": "json_clear(key, path = nil)", + "params": [ + { + "name": "key", + "type": "String", + "description": "" + }, + { + "name": "path", + "type": "String", + "description": "an optional JSONPath (defaults to the root \"$\")" + } + ], + "returns": { + "type": "Integer", + "description": "the number of values cleared" + } + } ] } }, @@ -69695,6 +69907,27 @@ "description": "" } } + ], + "redis_rb": [ + { + "signature": "json_debug_memory(key, path = nil)", + "params": [ + { + "name": "key", + "type": "String", + "description": "" + }, + { + "name": "path", + "type": "String", + "description": "an optional JSONPath (defaults to the root)" + } + ], + "returns": { + "type": "Array, Integer", + "description": "the size(s) in bytes: an Array for a JSONPath, a single integer for a legacy path or when no path is given (0 for a missing key)" + } + } ] } }, @@ -70024,6 +70257,27 @@ "description": "The number of paths deleted." } } + ], + "redis_rb": [ + { + "signature": "json_del(key, path = nil)", + "params": [ + { + "name": "key", + "type": "String", + "description": "" + }, + { + "name": "path", + "type": "String", + "description": "an optional JSONPath (defaults to the root \"$\")" + } + ], + "returns": { + "type": "Integer", + "description": "the number of values deleted" + } + } ] } }, @@ -70096,6 +70350,27 @@ "description": "" } } + ], + "redis_rb": [ + { + "signature": "json_forget(key, path = nil)", + "params": [ + { + "name": "key", + "type": "String", + "description": "" + }, + { + "name": "path", + "type": "String", + "description": "an optional JSONPath (defaults to the root \"$\")" + } + ], + "returns": { + "type": "Integer", + "description": "the number of values deleted (alias of json_del)" + } + } ] } }, @@ -70523,6 +70798,32 @@ "description": "The JSON value at the requested path." } } + ], + "redis_rb": [ + { + "signature": "json_get(key, *paths, raw: false)", + "params": [ + { + "name": "key", + "type": "String", + "description": "" + }, + { + "name": "*paths", + "type": "Array", + "description": "zero or more JSONPath expressions; with none the whole document is returned" + }, + { + "name": "raw", + "type": "Boolean", + "description": "return the unparsed JSON string instead of a parsed Ruby object" + } + ], + "returns": { + "type": "Object, String, nil", + "description": "the parsed JSON value (or the raw JSON string when raw is true), or nil if the key does not exist" + } + } ] } }, @@ -70896,6 +71197,37 @@ "description": "" } } + ], + "redis_rb": [ + { + "signature": "json_merge(key, path, value, raw: false)", + "params": [ + { + "name": "key", + "type": "String", + "description": "" + }, + { + "name": "path", + "type": "String", + "description": "a JSONPath (must be \"$\" when creating a new key)" + }, + { + "name": "value", + "type": "Object", + "description": "a JSON-serializable Ruby object, or a pre-encoded JSON string when raw is true" + }, + { + "name": "raw", + "type": "Boolean", + "description": "treat value as an already-encoded JSON string and send it as-is" + } + ], + "returns": { + "type": "String", + "description": "the raw \"OK\" reply" + } + } ] } }, @@ -71236,6 +71568,32 @@ "description": "The JSON values found at the requested path across the requested keys." } } + ], + "redis_rb": [ + { + "signature": "json_mget(*keys, path, raw: false)", + "params": [ + { + "name": "*keys", + "type": "Array", + "description": "one or more keys to read" + }, + { + "name": "path", + "type": "String", + "description": "a single JSONPath applied to every key" + }, + { + "name": "raw", + "type": "Boolean", + "description": "return the unparsed JSON strings instead of parsed Ruby objects" + } + ], + "returns": { + "type": "Array", + "description": "one value per key (nil for a missing key/path)" + } + } ] } }, @@ -71405,6 +71763,27 @@ "description": "The status reply for the multi-set operation." } } + ], + "redis_rb": [ + { + "signature": "json_mset(*args, raw: false)", + "params": [ + { + "name": "*args", + "type": "Array", + "description": "a flat list of key, path, value triplets" + }, + { + "name": "raw", + "type": "Boolean", + "description": "treat each value as an already-encoded JSON string" + } + ], + "returns": { + "type": "String", + "description": "the raw \"OK\" reply" + } + } ] } }, @@ -71724,6 +72103,32 @@ "description": "The updated numeric value." } } + ], + "redis_rb": [ + { + "signature": "json_numincrby(key, path, number)", + "params": [ + { + "name": "key", + "type": "String", + "description": "" + }, + { + "name": "path", + "type": "String", + "description": "a JSONPath to the numeric value(s)" + }, + { + "name": "number", + "type": "Numeric", + "description": "the amount to add" + } + ], + "returns": { + "type": "Array, Numeric, nil", + "description": "the new value(s): an Array for a JSONPath, a single number for a legacy path; nil (or nil element) for a non-numeric match" + } + } ] } }, @@ -72039,6 +72444,27 @@ "description": "The keys contained in the JSON object at the requested path." } } + ], + "redis_rb": [ + { + "signature": "json_objkeys(key, path = nil)", + "params": [ + { + "name": "key", + "type": "String", + "description": "" + }, + { + "name": "path", + "type": "String", + "description": "an optional JSONPath (defaults to the root)" + } + ], + "returns": { + "type": "Array", + "description": "an array of key-name arrays (JSONPath) or a single array of key names (legacy path); nil for a non-object match" + } + } ] } }, @@ -72354,6 +72780,27 @@ "description": "The number of keys in the JSON object at the requested path." } } + ], + "redis_rb": [ + { + "signature": "json_objlen(key, path = nil)", + "params": [ + { + "name": "key", + "type": "String", + "description": "" + }, + { + "name": "path", + "type": "String", + "description": "an optional JSONPath (defaults to the root)" + } + ], + "returns": { + "type": "Array, Integer, nil", + "description": "the key count(s): an Array for a JSONPath, a single integer for a legacy path; nil for a non-object match" + } + } ] } }, @@ -73170,6 +73617,47 @@ "description": "The status reply for the set operation." } } + ], + "redis_rb": [ + { + "signature": "json_set(key, path, value, nx: false, xx: false, raw: false)", + "params": [ + { + "name": "key", + "type": "String", + "description": "" + }, + { + "name": "path", + "type": "String", + "description": "a JSONPath, e.g. \"$\" for the document root" + }, + { + "name": "value", + "type": "Object", + "description": "a JSON-serializable Ruby object, or a pre-encoded JSON string when raw is true" + }, + { + "name": "nx", + "type": "Boolean", + "description": "only set when the path does not already exist" + }, + { + "name": "xx", + "type": "Boolean", + "description": "only set when the path already exists" + }, + { + "name": "raw", + "type": "Boolean", + "description": "treat value as an already-encoded JSON string and send it as-is" + } + ], + "returns": { + "type": "Boolean, String", + "description": "when nx or xx is given, true on success and false when the condition was not met; otherwise the raw \"OK\" reply" + } + } ] } }, @@ -73694,6 +74182,37 @@ "description": "The string length after the append operation." } } + ], + "redis_rb": [ + { + "signature": "json_strappend(key, path, value, raw: false)", + "params": [ + { + "name": "key", + "type": "String", + "description": "" + }, + { + "name": "path", + "type": "String", + "description": "a JSONPath to the target string(s)" + }, + { + "name": "value", + "type": "String", + "description": "the string to append" + }, + { + "name": "raw", + "type": "Boolean", + "description": "treat value as an already-encoded JSON string and send it as-is" + } + ], + "returns": { + "type": "Array, Integer, nil", + "description": "the new string length(s): an Array for a JSONPath, a single integer for a legacy path; nil for a non-string match" + } + } ] } }, @@ -74009,6 +74528,27 @@ "description": "The length of the JSON string at the requested path." } } + ], + "redis_rb": [ + { + "signature": "json_strlen(key, path = nil)", + "params": [ + { + "name": "key", + "type": "String", + "description": "" + }, + { + "name": "path", + "type": "String", + "description": "an optional JSONPath (defaults to the root)" + } + ], + "returns": { + "type": "Array, Integer, nil", + "description": "the string length(s): an Array for a JSONPath, a single integer for a legacy path; nil for a non-string match" + } + } ] } }, @@ -74268,6 +74808,27 @@ "description": "The toggled boolean value." } } + ], + "redis_rb": [ + { + "signature": "json_toggle(key, path)", + "params": [ + { + "name": "key", + "type": "String", + "description": "" + }, + { + "name": "path", + "type": "String", + "description": "a JSONPath to the target boolean(s)" + } + ], + "returns": { + "type": "Array, Object, nil", + "description": "1/0 for the new true/false value(s): an Array for a JSONPath, a single value for a legacy path; nil for a non-boolean match" + } + } ] } }, @@ -74583,6 +75144,27 @@ "description": "The JSON type at the requested path." } } + ], + "redis_rb": [ + { + "signature": "json_type(key, path = nil)", + "params": [ + { + "name": "key", + "type": "String", + "description": "" + }, + { + "name": "path", + "type": "String", + "description": "an optional JSONPath (defaults to the root)" + } + ], + "returns": { + "type": "Array, String, nil", + "description": "the type name(s): an Array for a JSONPath, a single string for a legacy path" + } + } ] } }, diff --git a/data/command-api-mapping/JSON.ARRAPPEND.json b/data/command-api-mapping/JSON.ARRAPPEND.json index 4ca06e9a0d..664f8df97e 100644 --- a/data/command-api-mapping/JSON.ARRAPPEND.json +++ b/data/command-api-mapping/JSON.ARRAPPEND.json @@ -505,6 +505,37 @@ "description": "The array length after the append operation." } } + ], + "redis_rb": [ + { + "signature": "json_arrappend(key, path, *values, raw: false)", + "params": [ + { + "name": "key", + "type": "String", + "description": "" + }, + { + "name": "path", + "type": "String", + "description": "a JSONPath to the target array" + }, + { + "name": "*values", + "type": "Array", + "description": "one or more JSON values to append" + }, + { + "name": "raw", + "type": "Boolean", + "description": "treat each value as an already-encoded JSON string" + } + ], + "returns": { + "type": "Array, Integer", + "description": "the new array length(s); an Array for a JSONPath, a single Integer for a legacy path (nil for a match that is not an array)" + } + } ] } } diff --git a/data/command-api-mapping/JSON.ARRINDEX.json b/data/command-api-mapping/JSON.ARRINDEX.json index fcd603b3d8..913eb7c330 100644 --- a/data/command-api-mapping/JSON.ARRINDEX.json +++ b/data/command-api-mapping/JSON.ARRINDEX.json @@ -663,6 +663,47 @@ "description": "The index of the first matching element in the requested range." } } + ], + "redis_rb": [ + { + "signature": "json_arrindex(key, path, value, start: nil, stop: nil, raw: false)", + "params": [ + { + "name": "key", + "type": "String", + "description": "" + }, + { + "name": "path", + "type": "String", + "description": "a JSONPath to the target array" + }, + { + "name": "value", + "type": "Object", + "description": "the scalar JSON value to search for" + }, + { + "name": "start", + "type": "Integer", + "description": "optional inclusive start index" + }, + { + "name": "stop", + "type": "Integer", + "description": "optional exclusive stop index" + }, + { + "name": "raw", + "type": "Boolean", + "description": "treat value as an already-encoded JSON string" + } + ], + "returns": { + "type": "Array, Integer", + "description": "the index/indices of the first match (-1 if not found)" + } + } ] } } diff --git a/data/command-api-mapping/JSON.ARRINSERT.json b/data/command-api-mapping/JSON.ARRINSERT.json index bdbf7b80bf..36a9b98483 100644 --- a/data/command-api-mapping/JSON.ARRINSERT.json +++ b/data/command-api-mapping/JSON.ARRINSERT.json @@ -466,6 +466,42 @@ "description": "The array length after the insert operation." } } + ], + "redis_rb": [ + { + "signature": "json_arrinsert(key, path, index, *values, raw: false)", + "params": [ + { + "name": "key", + "type": "String", + "description": "" + }, + { + "name": "path", + "type": "String", + "description": "a JSONPath to the target array" + }, + { + "name": "index", + "type": "Integer", + "description": "the position to insert before" + }, + { + "name": "*values", + "type": "Array", + "description": "one or more JSON values to insert" + }, + { + "name": "raw", + "type": "Boolean", + "description": "treat each value as an already-encoded JSON string" + } + ], + "returns": { + "type": "Array, Integer", + "description": "the new array length(s)" + } + } ] } } diff --git a/data/command-api-mapping/JSON.ARRLEN.json b/data/command-api-mapping/JSON.ARRLEN.json index d98f1c099e..93187fa63d 100644 --- a/data/command-api-mapping/JSON.ARRLEN.json +++ b/data/command-api-mapping/JSON.ARRLEN.json @@ -310,6 +310,27 @@ "description": "The length of the JSON array at the requested path." } } + ], + "redis_rb": [ + { + "signature": "json_arrlen(key, path = nil)", + "params": [ + { + "name": "key", + "type": "String", + "description": "" + }, + { + "name": "path", + "type": "String", + "description": "an optional JSONPath (defaults to the root \"$\")" + } + ], + "returns": { + "type": "Array, Integer, nil", + "description": "the array length(s); nil for a match that is not an array, or when the key/path does not exist" + } + } ] } } diff --git a/data/command-api-mapping/JSON.ARRPOP.json b/data/command-api-mapping/JSON.ARRPOP.json index 7fc9764159..b6677f4287 100644 --- a/data/command-api-mapping/JSON.ARRPOP.json +++ b/data/command-api-mapping/JSON.ARRPOP.json @@ -460,6 +460,37 @@ "description": "The removed JSON element." } } + ], + "redis_rb": [ + { + "signature": "json_arrpop(key, path = nil, index = nil, raw: false)", + "params": [ + { + "name": "key", + "type": "String", + "description": "" + }, + { + "name": "path", + "type": "String", + "description": "an optional JSONPath to the target array (defaults to the root \"$\")" + }, + { + "name": "index", + "type": "Integer", + "description": "an optional position to pop from (defaults to -1, the last element)" + }, + { + "name": "raw", + "type": "Boolean", + "description": "return the unparsed JSON string(s) instead of parsed Ruby objects" + } + ], + "returns": { + "type": "Array, Object, nil", + "description": "the popped value(s); an Array for a JSONPath, a single value for a legacy path, nil for an empty array or a non-array match" + } + } ] } } diff --git a/data/command-api-mapping/JSON.ARRTRIM.json b/data/command-api-mapping/JSON.ARRTRIM.json index b975fa2b1e..11dabb8c6f 100644 --- a/data/command-api-mapping/JSON.ARRTRIM.json +++ b/data/command-api-mapping/JSON.ARRTRIM.json @@ -349,6 +349,37 @@ "description": "The length of the array after trimming." } } + ], + "redis_rb": [ + { + "signature": "json_arrtrim(key, path, start, stop)", + "params": [ + { + "name": "key", + "type": "String", + "description": "" + }, + { + "name": "path", + "type": "String", + "description": "a JSONPath to the target array" + }, + { + "name": "start", + "type": "Integer", + "description": "inclusive index of the first element to keep" + }, + { + "name": "stop", + "type": "Integer", + "description": "inclusive index of the last element to keep" + } + ], + "returns": { + "type": "Array, Integer", + "description": "the new array length(s)" + } + } ] } } diff --git a/data/command-api-mapping/JSON.CLEAR.json b/data/command-api-mapping/JSON.CLEAR.json index 557336d3e2..629fc1097b 100644 --- a/data/command-api-mapping/JSON.CLEAR.json +++ b/data/command-api-mapping/JSON.CLEAR.json @@ -324,6 +324,27 @@ "description": "The number of values cleared." } } + ], + "redis_rb": [ + { + "signature": "json_clear(key, path = nil)", + "params": [ + { + "name": "key", + "type": "String", + "description": "" + }, + { + "name": "path", + "type": "String", + "description": "an optional JSONPath (defaults to the root \"$\")" + } + ], + "returns": { + "type": "Integer", + "description": "the number of values cleared" + } + } ] } } diff --git a/data/command-api-mapping/JSON.DEBUG MEMORY.json b/data/command-api-mapping/JSON.DEBUG MEMORY.json index 9a9f5fb9a4..6585b6095d 100644 --- a/data/command-api-mapping/JSON.DEBUG MEMORY.json +++ b/data/command-api-mapping/JSON.DEBUG MEMORY.json @@ -100,6 +100,27 @@ "description": "" } } + ], + "redis_rb": [ + { + "signature": "json_debug_memory(key, path = nil)", + "params": [ + { + "name": "key", + "type": "String", + "description": "" + }, + { + "name": "path", + "type": "String", + "description": "an optional JSONPath (defaults to the root)" + } + ], + "returns": { + "type": "Array, Integer", + "description": "the size(s) in bytes: an Array for a JSONPath, a single integer for a legacy path or when no path is given (0 for a missing key)" + } + } ] } } diff --git a/data/command-api-mapping/JSON.DEL.json b/data/command-api-mapping/JSON.DEL.json index 178d01591d..adf1a67702 100644 --- a/data/command-api-mapping/JSON.DEL.json +++ b/data/command-api-mapping/JSON.DEL.json @@ -324,6 +324,27 @@ "description": "The number of paths deleted." } } + ], + "redis_rb": [ + { + "signature": "json_del(key, path = nil)", + "params": [ + { + "name": "key", + "type": "String", + "description": "" + }, + { + "name": "path", + "type": "String", + "description": "an optional JSONPath (defaults to the root \"$\")" + } + ], + "returns": { + "type": "Integer", + "description": "the number of values deleted" + } + } ] } } diff --git a/data/command-api-mapping/JSON.FORGET.json b/data/command-api-mapping/JSON.FORGET.json index cd9128da54..2db712d116 100644 --- a/data/command-api-mapping/JSON.FORGET.json +++ b/data/command-api-mapping/JSON.FORGET.json @@ -67,6 +67,27 @@ "description": "" } } + ], + "redis_rb": [ + { + "signature": "json_forget(key, path = nil)", + "params": [ + { + "name": "key", + "type": "String", + "description": "" + }, + { + "name": "path", + "type": "String", + "description": "an optional JSONPath (defaults to the root \"$\")" + } + ], + "returns": { + "type": "Integer", + "description": "the number of values deleted (alias of json_del)" + } + } ] } } diff --git a/data/command-api-mapping/JSON.GET.json b/data/command-api-mapping/JSON.GET.json index 7ca201022d..1fbe258d7b 100644 --- a/data/command-api-mapping/JSON.GET.json +++ b/data/command-api-mapping/JSON.GET.json @@ -422,6 +422,32 @@ "description": "The JSON value at the requested path." } } + ], + "redis_rb": [ + { + "signature": "json_get(key, *paths, raw: false)", + "params": [ + { + "name": "key", + "type": "String", + "description": "" + }, + { + "name": "*paths", + "type": "Array", + "description": "zero or more JSONPath expressions; with none the whole document is returned" + }, + { + "name": "raw", + "type": "Boolean", + "description": "return the unparsed JSON string instead of a parsed Ruby object" + } + ], + "returns": { + "type": "Object, String, nil", + "description": "the parsed JSON value (or the raw JSON string when raw is true), or nil if the key does not exist" + } + } ] } } diff --git a/data/command-api-mapping/JSON.MERGE.json b/data/command-api-mapping/JSON.MERGE.json index 23d47efd58..cc163cee8c 100644 --- a/data/command-api-mapping/JSON.MERGE.json +++ b/data/command-api-mapping/JSON.MERGE.json @@ -368,6 +368,37 @@ "description": "" } } + ], + "redis_rb": [ + { + "signature": "json_merge(key, path, value, raw: false)", + "params": [ + { + "name": "key", + "type": "String", + "description": "" + }, + { + "name": "path", + "type": "String", + "description": "a JSONPath (must be \"$\" when creating a new key)" + }, + { + "name": "value", + "type": "Object", + "description": "a JSON-serializable Ruby object, or a pre-encoded JSON string when raw is true" + }, + { + "name": "raw", + "type": "Boolean", + "description": "treat value as an already-encoded JSON string and send it as-is" + } + ], + "returns": { + "type": "String", + "description": "the raw \"OK\" reply" + } + } ] } } diff --git a/data/command-api-mapping/JSON.MGET.json b/data/command-api-mapping/JSON.MGET.json index 0cf064a30e..8145190e79 100644 --- a/data/command-api-mapping/JSON.MGET.json +++ b/data/command-api-mapping/JSON.MGET.json @@ -335,6 +335,32 @@ "description": "The JSON values found at the requested path across the requested keys." } } + ], + "redis_rb": [ + { + "signature": "json_mget(*keys, path, raw: false)", + "params": [ + { + "name": "*keys", + "type": "Array", + "description": "one or more keys to read" + }, + { + "name": "path", + "type": "String", + "description": "a single JSONPath applied to every key" + }, + { + "name": "raw", + "type": "Boolean", + "description": "return the unparsed JSON strings instead of parsed Ruby objects" + } + ], + "returns": { + "type": "Array", + "description": "one value per key (nil for a missing key/path)" + } + } ] } } diff --git a/data/command-api-mapping/JSON.MSET.json b/data/command-api-mapping/JSON.MSET.json index cc87c13c60..c7309486c8 100644 --- a/data/command-api-mapping/JSON.MSET.json +++ b/data/command-api-mapping/JSON.MSET.json @@ -164,6 +164,27 @@ "description": "The status reply for the multi-set operation." } } + ], + "redis_rb": [ + { + "signature": "json_mset(*args, raw: false)", + "params": [ + { + "name": "*args", + "type": "Array", + "description": "a flat list of key, path, value triplets" + }, + { + "name": "raw", + "type": "Boolean", + "description": "treat each value as an already-encoded JSON string" + } + ], + "returns": { + "type": "String", + "description": "the raw \"OK\" reply" + } + } ] } } diff --git a/data/command-api-mapping/JSON.NUMINCRBY.json b/data/command-api-mapping/JSON.NUMINCRBY.json index fc01c50ba6..0146c493e9 100644 --- a/data/command-api-mapping/JSON.NUMINCRBY.json +++ b/data/command-api-mapping/JSON.NUMINCRBY.json @@ -314,6 +314,32 @@ "description": "The updated numeric value." } } + ], + "redis_rb": [ + { + "signature": "json_numincrby(key, path, number)", + "params": [ + { + "name": "key", + "type": "String", + "description": "" + }, + { + "name": "path", + "type": "String", + "description": "a JSONPath to the numeric value(s)" + }, + { + "name": "number", + "type": "Numeric", + "description": "the amount to add" + } + ], + "returns": { + "type": "Array, Numeric, nil", + "description": "the new value(s): an Array for a JSONPath, a single number for a legacy path; nil (or nil element) for a non-numeric match" + } + } ] } } diff --git a/data/command-api-mapping/JSON.OBJKEYS.json b/data/command-api-mapping/JSON.OBJKEYS.json index 6a284bc591..8d73aaecc8 100644 --- a/data/command-api-mapping/JSON.OBJKEYS.json +++ b/data/command-api-mapping/JSON.OBJKEYS.json @@ -310,6 +310,27 @@ "description": "The keys contained in the JSON object at the requested path." } } + ], + "redis_rb": [ + { + "signature": "json_objkeys(key, path = nil)", + "params": [ + { + "name": "key", + "type": "String", + "description": "" + }, + { + "name": "path", + "type": "String", + "description": "an optional JSONPath (defaults to the root)" + } + ], + "returns": { + "type": "Array", + "description": "an array of key-name arrays (JSONPath) or a single array of key names (legacy path); nil for a non-object match" + } + } ] } } diff --git a/data/command-api-mapping/JSON.OBJLEN.json b/data/command-api-mapping/JSON.OBJLEN.json index de7dc66308..4105dc8c78 100644 --- a/data/command-api-mapping/JSON.OBJLEN.json +++ b/data/command-api-mapping/JSON.OBJLEN.json @@ -310,6 +310,27 @@ "description": "The number of keys in the JSON object at the requested path." } } + ], + "redis_rb": [ + { + "signature": "json_objlen(key, path = nil)", + "params": [ + { + "name": "key", + "type": "String", + "description": "" + }, + { + "name": "path", + "type": "String", + "description": "an optional JSONPath (defaults to the root)" + } + ], + "returns": { + "type": "Array, Integer, nil", + "description": "the key count(s): an Array for a JSONPath, a single integer for a legacy path; nil for a non-object match" + } + } ] } } diff --git a/data/command-api-mapping/JSON.SET.json b/data/command-api-mapping/JSON.SET.json index 9f3708ce50..a22ae454cb 100644 --- a/data/command-api-mapping/JSON.SET.json +++ b/data/command-api-mapping/JSON.SET.json @@ -744,6 +744,47 @@ "description": "The status reply for the set operation." } } + ], + "redis_rb": [ + { + "signature": "json_set(key, path, value, nx: false, xx: false, raw: false)", + "params": [ + { + "name": "key", + "type": "String", + "description": "" + }, + { + "name": "path", + "type": "String", + "description": "a JSONPath, e.g. \"$\" for the document root" + }, + { + "name": "value", + "type": "Object", + "description": "a JSON-serializable Ruby object, or a pre-encoded JSON string when raw is true" + }, + { + "name": "nx", + "type": "Boolean", + "description": "only set when the path does not already exist" + }, + { + "name": "xx", + "type": "Boolean", + "description": "only set when the path already exists" + }, + { + "name": "raw", + "type": "Boolean", + "description": "treat value as an already-encoded JSON string and send it as-is" + } + ], + "returns": { + "type": "Boolean, String", + "description": "when nx or xx is given, true on success and false when the condition was not met; otherwise the raw \"OK\" reply" + } + } ] } } diff --git a/data/command-api-mapping/JSON.STRAPPEND.json b/data/command-api-mapping/JSON.STRAPPEND.json index 28d5272733..abc6876394 100644 --- a/data/command-api-mapping/JSON.STRAPPEND.json +++ b/data/command-api-mapping/JSON.STRAPPEND.json @@ -519,6 +519,37 @@ "description": "The string length after the append operation." } } + ], + "redis_rb": [ + { + "signature": "json_strappend(key, path, value, raw: false)", + "params": [ + { + "name": "key", + "type": "String", + "description": "" + }, + { + "name": "path", + "type": "String", + "description": "a JSONPath to the target string(s)" + }, + { + "name": "value", + "type": "String", + "description": "the string to append" + }, + { + "name": "raw", + "type": "Boolean", + "description": "treat value as an already-encoded JSON string and send it as-is" + } + ], + "returns": { + "type": "Array, Integer, nil", + "description": "the new string length(s): an Array for a JSONPath, a single integer for a legacy path; nil for a non-string match" + } + } ] } } diff --git a/data/command-api-mapping/JSON.STRLEN.json b/data/command-api-mapping/JSON.STRLEN.json index bb3682336b..13c8acaa8d 100644 --- a/data/command-api-mapping/JSON.STRLEN.json +++ b/data/command-api-mapping/JSON.STRLEN.json @@ -310,6 +310,27 @@ "description": "The length of the JSON string at the requested path." } } + ], + "redis_rb": [ + { + "signature": "json_strlen(key, path = nil)", + "params": [ + { + "name": "key", + "type": "String", + "description": "" + }, + { + "name": "path", + "type": "String", + "description": "an optional JSONPath (defaults to the root)" + } + ], + "returns": { + "type": "Array, Integer, nil", + "description": "the string length(s): an Array for a JSONPath, a single integer for a legacy path; nil for a non-string match" + } + } ] } } diff --git a/data/command-api-mapping/JSON.TOGGLE.json b/data/command-api-mapping/JSON.TOGGLE.json index bc0c30cdee..2dd8ca413f 100644 --- a/data/command-api-mapping/JSON.TOGGLE.json +++ b/data/command-api-mapping/JSON.TOGGLE.json @@ -254,6 +254,27 @@ "description": "The toggled boolean value." } } + ], + "redis_rb": [ + { + "signature": "json_toggle(key, path)", + "params": [ + { + "name": "key", + "type": "String", + "description": "" + }, + { + "name": "path", + "type": "String", + "description": "a JSONPath to the target boolean(s)" + } + ], + "returns": { + "type": "Array, Object, nil", + "description": "1/0 for the new true/false value(s): an Array for a JSONPath, a single value for a legacy path; nil for a non-boolean match" + } + } ] } } diff --git a/data/command-api-mapping/JSON.TYPE.json b/data/command-api-mapping/JSON.TYPE.json index bffe86d968..7bc33fcb0a 100644 --- a/data/command-api-mapping/JSON.TYPE.json +++ b/data/command-api-mapping/JSON.TYPE.json @@ -310,6 +310,27 @@ "description": "The JSON type at the requested path." } } + ], + "redis_rb": [ + { + "signature": "json_type(key, path = nil)", + "params": [ + { + "name": "key", + "type": "String", + "description": "" + }, + { + "name": "path", + "type": "String", + "description": "an optional JSONPath (defaults to the root)" + } + ], + "returns": { + "type": "Array, String, nil", + "description": "the type name(s): an Array for a JSONPath, a single string for a legacy path" + } + } ] } } diff --git a/local_examples/tmp/datatypes/json/dt_json.rb b/local_examples/tmp/datatypes/json/dt_json.rb new file mode 100644 index 0000000000..6151edc809 --- /dev/null +++ b/local_examples/tmp/datatypes/json/dt_json.rb @@ -0,0 +1,367 @@ +# EXAMPLE: json_tutorial +# HIDE_START +require 'redis' + +r = Redis.new +# HIDE_END + +# REMOVE_START +def assert_equal(expected, actual) + raise "Expected #{expected.inspect}, got #{actual.inspect}" unless actual == expected +end + +r.del('bike', 'crashes', 'newbike', 'riders', 'bike:1', 'bikes:inventory') +# REMOVE_END + +# STEP_START set_get +res1 = r.json_set('bike', '$', 'Hyperion') +puts res1 # >>> OK + +res2 = r.json_get('bike', '$') +p res2 # >>> ["Hyperion"] + +res3 = r.json_type('bike', '$') +p res3 # >>> ["string"] +# STEP_END + +# REMOVE_START +assert_equal(['Hyperion'], res2) +# REMOVE_END + +# STEP_START str +res4 = r.json_strlen('bike', '$') +p res4 # >>> [8] + +res5 = r.json_strappend('bike', '$', ' (Enduro bikes)') +p res5 # >>> [23] + +res6 = r.json_get('bike', '$') +p res6 # >>> ["Hyperion (Enduro bikes)"] +# STEP_END + +# REMOVE_START +assert_equal(['Hyperion (Enduro bikes)'], res6) +# REMOVE_END + +# STEP_START num +res7 = r.json_set('crashes', '$', 0) +puts res7 # >>> OK + +res8 = r.json_numincrby('crashes', '$', 1) +p res8 # >>> [1] + +res9 = r.json_numincrby('crashes', '$', 1.5) +p res9 # >>> [2.5] + +res10 = r.json_numincrby('crashes', '$', -0.75) +p res10 # >>> [1.75] +# STEP_END + +# REMOVE_START +assert_equal([1.75], res10) +# REMOVE_END + +# STEP_START arr +res11 = r.json_set('newbike', '$', ['Deimos', { 'crashes' => 0 }, nil]) +puts res11 # >>> OK + +res12 = r.json_get('newbike', '$') +p res12 # >>> [["Deimos", {"crashes"=>0}, nil]] + +res13 = r.json_get('newbike', '$[1].crashes') +p res13 # >>> [0] + +res14 = r.json_del('newbike', '$.[-1]') +p res14 # >>> 1 + +res15 = r.json_get('newbike', '$') +p res15 # >>> [["Deimos", {"crashes"=>0}]] +# STEP_END + +# REMOVE_START +assert_equal([['Deimos', { 'crashes' => 0 }]], res15) +# REMOVE_END + +# STEP_START arr2 +res16 = r.json_set('riders', '$', []) +puts res16 # >>> OK + +res17 = r.json_arrappend('riders', '$', 'Norem') +p res17 # >>> [1] + +res18 = r.json_get('riders', '$') +p res18 # >>> [["Norem"]] + +res19 = r.json_arrinsert('riders', '$', 1, 'Prickett', 'Royce', 'Castilla') +p res19 # >>> [4] + +res20 = r.json_get('riders', '$') +p res20 # >>> [["Norem", "Prickett", "Royce", "Castilla"]] + +res21 = r.json_arrtrim('riders', '$', 1, 1) +p res21 # >>> [1] + +res22 = r.json_get('riders', '$') +p res22 # >>> [["Prickett"]] + +res23 = r.json_arrpop('riders', '$') +p res23 # >>> ["Prickett"] + +res24 = r.json_arrpop('riders', '$') +p res24 # >>> [nil] +# STEP_END + +# REMOVE_START +assert_equal([nil], res24) +# REMOVE_END + +# STEP_START obj +res25 = r.json_set('bike:1', '$', { 'model' => 'Deimos', 'brand' => 'Ergonom', 'price' => 4972 }) +puts res25 # >>> OK + +res26 = r.json_objlen('bike:1', '$') +p res26 # >>> [3] + +res27 = r.json_objkeys('bike:1', '$') +p res27 # >>> [["model", "brand", "price"]] +# STEP_END + +# REMOVE_START +assert_equal([%w[model brand price]], res27) +# REMOVE_END + +# STEP_START set_bikes +# HIDE_START +inventory_json = { + 'inventory' => { + 'mountain_bikes' => [ + { + 'id' => 'bike:1', + 'model' => 'Phoebe', + 'description' => 'This is a mid-travel trail slayer that is a fantastic ' \ + 'daily driver or one bike quiver. The Shimano Claris 8-speed groupset ' \ + 'gives plenty of gear range to tackle hills and there’s room for ' \ + 'mudguards and a rack too. This is the bike for the rider who wants ' \ + 'trail manners with low fuss ownership.', + 'price' => 1920, + 'specs' => { 'material' => 'carbon', 'weight' => 13.1 }, + 'colors' => %w[black silver] + }, + { + 'id' => 'bike:2', + 'model' => 'Quaoar', + 'description' => 'Redesigned for the 2020 model year, this bike ' \ + "impressed our testers and is the best all-around trail bike we've " \ + 'ever tested. The Shimano gear system effectively does away with an ' \ + 'external cassette, so is super low maintenance in terms of wear ' \ + "and tear. All in all it's an impressive package for the price, " \ + 'making it very competitive.', + 'price' => 2072, + 'specs' => { 'material' => 'aluminium', 'weight' => 7.9 }, + 'colors' => %w[black white] + }, + { + 'id' => 'bike:3', + 'model' => 'Weywot', + 'description' => 'This bike gives kids aged six years and older ' \ + 'a durable and uberlight mountain bike for their first experience ' \ + 'on tracks and easy cruising through forests and fields. A set of ' \ + 'powerful Shimano hydraulic disc brakes provide ample stopping ' \ + "ability. If you're after a budget option, this is one of the best " \ + 'bikes you could get.', + 'price' => 3264, + 'specs' => { 'material' => 'alloy', 'weight' => 13.8 } + } + ], + 'commuter_bikes' => [ + { + 'id' => 'bike:4', + 'model' => 'Salacia', + 'description' => 'This bike is a great option for anyone who just ' \ + 'wants a bike to get about on With a slick-shifting Claris gears ' \ + 'from Shimano’s, this is a bike which doesn’t break the ' \ + "bank and delivers craved performance. It's for the rider " \ + 'who wants both efficiency and capability.', + 'price' => 1475, + 'specs' => { 'material' => 'aluminium', 'weight' => 16.6 }, + 'colors' => %w[black silver] + }, + { + 'id' => 'bike:5', + 'model' => 'Mimas', + 'description' => 'A real joy to ride, this bike got very high ' \ + 'scores in last years Bike of the year report. The carefully ' \ + 'crafted 50-34 tooth chainset and 11-32 tooth cassette give an ' \ + 'easy-on-the-legs bottom gear for climbing, and the high-quality ' \ + 'Vittoria Zaffiro tires give balance and grip.It includes ' \ + 'a low-step frame , our memory foam seat, bump-resistant shocks and ' \ + 'conveniently placed thumb throttle. Put it all together and you ' \ + 'get a bike that helps redefine what can be done for this price.', + 'price' => 3941, + 'specs' => { 'material' => 'alloy', 'weight' => 11.6 } + } + ] + } +} +# HIDE_END + +res1 = r.json_set('bikes:inventory', '$', inventory_json) +puts res1 # >>> OK +# STEP_END + +# STEP_START get_bikes +res2 = r.json_get('bikes:inventory', '$.inventory.*') +p res2 +# >>> [[{"id"=>"bike:1", "model"=>"Phoebe", +# >>> "description"=>"This is a mid-travel trail slayer... +# STEP_END + +# STEP_START get_mtnbikes +res3 = r.json_get('bikes:inventory', '$.inventory.mountain_bikes[*].model') +p res3 # >>> ["Phoebe", "Quaoar", "Weywot"] + +res4 = r.json_get('bikes:inventory', '$.inventory["mountain_bikes"][*].model') +p res4 # >>> ["Phoebe", "Quaoar", "Weywot"] + +res5 = r.json_get('bikes:inventory', '$..mountain_bikes[*].model') +p res5 # >>> ["Phoebe", "Quaoar", "Weywot"] +# STEP_END + +# REMOVE_START +assert_equal(%w[Phoebe Quaoar Weywot], res3) +assert_equal(%w[Phoebe Quaoar Weywot], res4) +assert_equal(%w[Phoebe Quaoar Weywot], res5) +# REMOVE_END + +# STEP_START get_models +res6 = r.json_get('bikes:inventory', '$..model') +p res6 # >>> ["Phoebe", "Quaoar", "Weywot", "Salacia", "Mimas"] +# STEP_END + +# REMOVE_START +assert_equal(%w[Phoebe Quaoar Weywot Salacia Mimas], res6) +# REMOVE_END + +# STEP_START get2mtnbikes +res7 = r.json_get('bikes:inventory', '$..mountain_bikes[0:2].model') +p res7 # >>> ["Phoebe", "Quaoar"] +# STEP_END + +# REMOVE_START +assert_equal(%w[Phoebe Quaoar], res7) +# REMOVE_END + +# STEP_START filter1 +res8 = r.json_get( + 'bikes:inventory', + '$..mountain_bikes[?(@.price < 3000 && @.specs.weight < 10)]' +) +p res8 +# >>> [{"id"=>"bike:2", "model"=>"Quaoar", +# >>> "description"=>"Redesigned for the 2020 model year... +# STEP_END + +# REMOVE_START +assert_equal( + [ + { + 'id' => 'bike:2', + 'model' => 'Quaoar', + 'description' => 'Redesigned for the 2020 model year, this bike impressed ' \ + "our testers and is the best all-around trail bike we've ever tested. " \ + 'The Shimano gear system effectively does away with an external cassette, ' \ + "so is super low maintenance in terms of wear and tear. All in all it's " \ + 'an impressive package for the price, making it very competitive.', + 'price' => 2072, + 'specs' => { 'material' => 'aluminium', 'weight' => 7.9 }, + 'colors' => %w[black white] + } + ], + res8 +) +# REMOVE_END + +# STEP_START filter2 +res9 = r.json_get('bikes:inventory', "$..[?(@.specs.material == 'alloy')].model") +p res9 # >>> ["Weywot", "Mimas"] +# STEP_END + +# REMOVE_START +assert_equal(%w[Weywot Mimas], res9) +# REMOVE_END + +# STEP_START filter3 +res10 = r.json_get('bikes:inventory', "$..[?(@.specs.material =~ '(?i)al')].model") +p res10 # >>> ["Quaoar", "Weywot", "Salacia", "Mimas"] +# STEP_END + +# REMOVE_START +assert_equal(%w[Quaoar Weywot Salacia Mimas], res10) +# REMOVE_END + +# STEP_START filter4 +res11 = r.json_set( + 'bikes:inventory', '$.inventory.mountain_bikes[0].regex_pat', '(?i)al' +) +res12 = r.json_set( + 'bikes:inventory', '$.inventory.mountain_bikes[1].regex_pat', '(?i)al' +) +res13 = r.json_set( + 'bikes:inventory', '$.inventory.mountain_bikes[2].regex_pat', '(?i)al' +) + +res14 = r.json_get( + 'bikes:inventory', + '$.inventory.mountain_bikes[?(@.specs.material =~ @.regex_pat)].model' +) +p res14 # >>> ["Quaoar", "Weywot"] +# STEP_END + +# REMOVE_START +assert_equal(%w[Quaoar Weywot], res14) +# REMOVE_END + +# STEP_START update_bikes +res15 = r.json_get('bikes:inventory', '$..price') +p res15 # >>> [1920, 2072, 3264, 1475, 3941] + +res16 = r.json_numincrby('bikes:inventory', '$..price', -100) +p res16 # >>> [1820, 1972, 3164, 1375, 3841] + +res17 = r.json_numincrby('bikes:inventory', '$..price', 100) +p res17 # >>> [1920, 2072, 3264, 1475, 3941] +# STEP_END + +# REMOVE_START +assert_equal([1920, 2072, 3264, 1475, 3941], res15) +assert_equal([1820, 1972, 3164, 1375, 3841], res16) +assert_equal([1920, 2072, 3264, 1475, 3941], res17) +# REMOVE_END + +# STEP_START update_filters1 +res18 = r.json_set('bikes:inventory', '$.inventory.*[?(@.price<2000)].price', 1500) +res19 = r.json_get('bikes:inventory', '$..price') +p res19 # >>> [1500, 2072, 3264, 1500, 3941] +# STEP_END + +# REMOVE_START +assert_equal([1500, 2072, 3264, 1500, 3941], res19) +# REMOVE_END + +# STEP_START update_filters2 +res20 = r.json_arrappend( + 'bikes:inventory', '$.inventory.*[?(@.price<2000)].colors', 'pink' +) +p res20 # >>> [3, 3] + +res21 = r.json_get('bikes:inventory', '$..[*].colors') +p res21 +# >>> [["black", "silver", "pink"], ["black", "white"], ["black", "silver", "pink"]] +# STEP_END + +# REMOVE_START +assert_equal( + [%w[black silver pink], %w[black white], %w[black silver pink]], + res21 +) +# REMOVE_END From a42f75e8f625d5a2748102d464b424b7028e2f9c Mon Sep 17 00:00:00 2001 From: Andy Stark Date: Fri, 17 Jul 2026 13:46:37 +0100 Subject: [PATCH 2/2] DOC-6842 Show the redis-rb raw: JSON option in the Ruby json_tutorial steps MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds raw: true examples to the set_get, arr, arr2, and obj steps of the Ruby JSON tutorial, per maintainer feedback (vladvildanov on PR #3629): raw input (pass an already-encoded JSON string, skipping serialization) and raw output (get the unparsed JSON string back instead of a Ruby object). These land only on the Ruby tab of those shared steps because raw: is a redis-rb-specific kwarg — the other clients on the json_tutorial set have no equivalent option, so the raw lines are deliberately not mirrored to their tabs. Re-verified end-to-end against the redis-rb fork (master) on Redis 8.8 — all asserts pass, including the five new raw ones. Directive: the raw: examples are Ruby-only by design (redis-rb-specific kwarg) — don't mirror them to other clients' tabs on these shared json_tutorial steps Ticket: DOC-6842 Co-Authored-By: Claude Opus 4.8 (1M context) --- local_examples/tmp/datatypes/json/dt_json.rb | 25 ++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/local_examples/tmp/datatypes/json/dt_json.rb b/local_examples/tmp/datatypes/json/dt_json.rb index 6151edc809..a4598f2f1c 100644 --- a/local_examples/tmp/datatypes/json/dt_json.rb +++ b/local_examples/tmp/datatypes/json/dt_json.rb @@ -22,10 +22,20 @@ def assert_equal(expected, actual) res3 = r.json_type('bike', '$') p res3 # >>> ["string"] + +# With raw: true, json_set accepts an already-encoded JSON string (skipping +# serialization) and json_get returns the unparsed JSON string rather than a +# Ruby object — useful when you store or forward plain JSON. +res_raw1 = r.json_set('bike', '$', '"Hyperion"', raw: true) +puts res_raw1 # >>> OK + +res_raw2 = r.json_get('bike', '$', raw: true) +puts res_raw2 # >>> ["Hyperion"] (a JSON string, not a Ruby array) # STEP_END # REMOVE_START assert_equal(['Hyperion'], res2) +assert_equal('["Hyperion"]', res_raw2) # REMOVE_END # STEP_START str @@ -76,10 +86,15 @@ def assert_equal(expected, actual) res15 = r.json_get('newbike', '$') p res15 # >>> [["Deimos", {"crashes"=>0}]] + +# The same raw: true option returns the array as unparsed JSON text. +res_raw3 = r.json_get('newbike', '$', raw: true) +puts res_raw3 # >>> [["Deimos",{"crashes":0}]] (a JSON string) # STEP_END # REMOVE_START assert_equal([['Deimos', { 'crashes' => 0 }]], res15) +assert_equal('[["Deimos",{"crashes":0}]]', res_raw3) # REMOVE_END # STEP_START arr2 @@ -109,10 +124,15 @@ def assert_equal(expected, actual) res24 = r.json_arrpop('riders', '$') p res24 # >>> [nil] + +# json_arrappend also takes a pre-encoded JSON value with raw: true. +res_raw4 = r.json_arrappend('riders', '$', '"Castilla"', raw: true) +p res_raw4 # >>> [1] # STEP_END # REMOVE_START assert_equal([nil], res24) +assert_equal([1], res_raw4) # REMOVE_END # STEP_START obj @@ -124,10 +144,15 @@ def assert_equal(expected, actual) res27 = r.json_objkeys('bike:1', '$') p res27 # >>> [["model", "brand", "price"]] + +# raw: true returns the object as unparsed JSON text. +res_raw5 = r.json_get('bike:1', '$', raw: true) +puts res_raw5 # >>> [{"model":"Deimos","brand":"Ergonom","price":4972}] (a JSON string) # STEP_END # REMOVE_START assert_equal([%w[model brand price]], res27) +assert_equal('[{"model":"Deimos","brand":"Ergonom","price":4972}]', res_raw5) # REMOVE_END # STEP_START set_bikes