diff --git a/build/example-test-harness/pom-lettuce-async.xml b/build/example-test-harness/pom-lettuce-async.xml
index b488bebbf5..b887da8f00 100644
--- a/build/example-test-harness/pom-lettuce-async.xml
+++ b/build/example-test-harness/pom-lettuce-async.xml
@@ -4,7 +4,8 @@
io.redislettuce-async1.0
17UTF-8
- io.lettucelettuce-core6.5.5.RELEASE
+ io.lettucelettuce-core7.7.0.RELEASE
+ com.fasterxml.jackson.corejackson-databind2.17.1
org.junit.jupiterjunit-jupiter5.10.2test
org.assertjassertj-core3.25.3test
diff --git a/build/example-test-harness/pom-lettuce-reactive.xml b/build/example-test-harness/pom-lettuce-reactive.xml
index e3f67e7892..b1fb7d1c25 100644
--- a/build/example-test-harness/pom-lettuce-reactive.xml
+++ b/build/example-test-harness/pom-lettuce-reactive.xml
@@ -4,7 +4,8 @@
io.redislettuce-reactive1.0
17UTF-8
- io.lettucelettuce-core6.5.5.RELEASE
+ io.lettucelettuce-core7.7.0.RELEASE
+ com.fasterxml.jackson.corejackson-databind2.17.1
org.junit.jupiterjunit-jupiter5.10.2test
org.assertjassertj-core3.25.3test
diff --git a/build/example-test-harness/run.sh b/build/example-test-harness/run.sh
index 3adf12f665..3c9cd83544 100755
--- a/build/example-test-harness/run.sh
+++ b/build/example-test-harness/run.sh
@@ -384,10 +384,12 @@ run_go() {
cp "$1" "$d/ex_test.go"
(cd "$d" && go mod tidy >/dev/null 2>&1 && go test ./... ) >"$LOG" 2>&1; rc=$?
}
-run_rust_sync() { rust_run "$WORK/rust-sync" "$1" 'redis = "1.3"' ; }
-run_rust_async(){ rust_run "$WORK/rust-async" "$1" 'redis = { version = "1.3", features = ["tokio-comp"] }
+run_rust_sync() { rust_run "$WORK/rust-sync" "$1" 'redis = { version = "1.3", features = ["json"] }
+serde_json = { version = "1", features = ["preserve_order"] }' ; }
+run_rust_async(){ rust_run "$WORK/rust-async" "$1" 'redis = { version = "1.3", features = ["tokio-comp", "json"] }
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
-futures-util = "0.3"' ; }
+futures-util = "0.3"
+serde_json = { version = "1", features = ["preserve_order"] }' ; }
rust_run() {
local d="$1" src="$2" deps="$3"; mkdir -p "$d/src"
if [ ! -f "$d/Cargo.toml" ]; then
diff --git a/content/develop/data-types/json/path.md b/content/develop/data-types/json/path.md
index 5133f7f136..b2a2ca64be 100644
--- a/content/develop/data-types/json/path.md
+++ b/content/develop/data-types/json/path.md
@@ -64,13 +64,13 @@ The following JSONPath syntax table was adapted from Goessner's [path syntax com
Beginning with Redis 8.10, the JSON data type supports a richer JSONPath syntax, with additional operators and functions:
-- Projection expressions at the top level of a JSONPath query
-- `==` and `!=` can now compare any literal, including array and object literals
-- Filter negation operator: `!`
-- `size`/`sizeof` and `empty` operators on string, array, object, and nodelist
-- `in` and `nin` operators: membership test on an array and nodelist
-- Operators on numbers: binary `-`, `+`, `*`, `/`, `%`, and unary `-` and `+`
-- Operator on object: `~`
+- [Projection expressions](#projection-expressions) at the top level of a JSONPath query
+- [`==` and `!=`](#comparing-array-and-object-literals) can now compare any literal, including array and object literals
+- Filter negation operator: [`!`](#negation-)
+- [`size`/`sizeof` and `empty`](#size-sizeof-and-empty) operators on string, array, object, and nodelist
+- [`in` and `nin`](#membership-in-and-nin) operators: membership test on an array and nodelist
+- [Operators on numbers](#arithmetic-operators): binary `-`, `+`, `*`, `/`, `%`, and unary `-` and `+`
+- Operator on object: [`~`](#get-keys-operator-)
- `length()` function on array, object, and string
- Functions on number: `abs()`, `ceiling()`, `floor()`
- Functions on string: `match()`, `search()`
@@ -80,7 +80,9 @@ Beginning with Redis 8.10, the JSON data type supports a richer JSONPath syntax,
- Function on object: `keys()`
- Function on nodelist: `count()`
- Function on nodelist with exactly one node: `value()`
-- Relations functions on array and nodelist: `subsetof()`, `anyof()`, `noneof()`
+- Relations functions on array and nodelist: [`subsetof()`, `anyof()`, `noneof()`](#set-relations-subsetof-anyof-and-noneof)
+
+These operators can be used within a filter expression (`?()`).
{{< warning >}}
Beginning with Redis 8.10, two changes to path parsing may affect existing queries:
@@ -99,131 +101,6 @@ The following rules apply to the operators and functions described below:
- **Arithmetic operators must be surrounded by spaces.** `@.a + 1` is addition, but `@.a+1` is a reference to a field literally named `a+1`, because the field-name character set includes characters such as `+`, `-`, `/`, `%`, `$`, `^`, `:`, and `_`.
- **Functions are arity-checked.** Calling a function with the wrong number of arguments produces *Nothing* rather than silently using a subset of the arguments.
-## Filter expression operators
-
-The following operators can be used within a filter expression (`?()`).
-
-### Negation `!`
-
-The `!` operator negates a filter condition. It binds more tightly than the logical `&&` and `||` operators, so use parentheses to negate a compound condition. A double negation (`!!`) is equivalent to an existence test.
-
-```
-> JSON.SET doc $ '[{"a":1,"b":1},{"b":2},{"a":1},{"c":3}]'
-OK
-> JSON.GET doc '$[?!@.a]'
-"[{\"b\":2},{\"c\":3}]"
-> JSON.GET doc '$[?!(@.a==1)]'
-"[{\"b\":2},{\"c\":3}]"
-> JSON.GET doc '$[?!@.a && @.b]'
-"[{\"b\":2}]"
-```
-
-### Comparing array and object literals
-
-The `==` and `!=` operators can compare any JSON literal, including array and object literals, on either side of the operator. Comparison is by structural (deep) equality. A comparison between values of different types is always false.
-
-```
-> JSON.SET doc $ '{"arrs":[[1],[2],[1,2],[1,[2]]],"objs":[{"x":1},{"x":2},{"y":1}]}'
-OK
-> JSON.GET doc '$.arrs[?(@ == [1])]'
-"[[1]]"
-> JSON.GET doc '$.arrs[?(@ == [1,[2]])]'
-"[[1,[2]]]"
-> JSON.GET doc '$.objs[?(@ == {"x":1})]'
-"[{\"x\":1}]"
-```
-
-### Arithmetic operators
-
-The binary operators `+`, `-`, `*`, `/`, and `%` and the unary operators `-` and `+` operate on numbers. Precedence, from highest to lowest, is: unary `-`/`+`, then `*`/`/`/`%`, then `+`/`-`. Use parentheses to override precedence. Division always produces a floating-point result. Division or modulo by zero produces *Nothing*, so no element matches.
-
-Remember that arithmetic operators must be surrounded by spaces (see [Evaluation semantics](#evaluation-semantics)).
-
-```
-> JSON.SET doc $ '[{"a":2,"b":3},{"a":5,"b":2}]'
-OK
-> JSON.GET doc '$[?@.a + 1 == 3]'
-"[{\"a\":2,\"b\":3}]"
-> JSON.GET doc '$[?@.a + @.b * 2 == 8]'
-"[{\"a\":2,\"b\":3}]"
-> JSON.GET doc '$[?(@.a + @.b) * 2 == 10]'
-"[{\"a\":2,\"b\":3}]"
-```
-
-### Membership: `in` and `nin`
-
-`value in array` matches when `value` is a member of `array`; `value nin array` is its strict negation. The right-hand side can be an array literal or a path to an array. Membership uses the same structural comparison and number coercion as `==`. If the right-hand side is not an array, `in` is false (so `nin` is true).
-
-```
-> JSON.SET doc $ '{"a":[1,2,3,4],"allow":[2,3]}'
-OK
-> JSON.GET doc '$.a[?@ in [2,4]]'
-"[2,4]"
-> JSON.GET doc '$.a[?@ nin [2,4]]'
-"[1,3]"
-> JSON.GET doc '$.a[?@ in $.allow]'
-"[2,3]"
-```
-
-### Set relations: `subsetof`, `anyof`, and `noneof`
-
-These operators compare two arrays (each side can be an array literal or a path to an array):
-
-- `subsetof` matches when every element of the left array is a member of the right array. An empty array is a subset of any array.
-- `anyof` matches when the two arrays have a non-empty intersection. An empty array never matches.
-- `noneof` matches when the two arrays have an empty intersection. An empty array always matches.
-
-```
-> JSON.SET doc $ '{"a":[[1,2],[1,5],[]]}'
-OK
-> JSON.GET doc '$.a[?@ subsetof [1,2,3]]'
-"[[1,2],[]]"
-> JSON.SET doc $ '{"a":[[1,9],[8,9],[]]}'
-OK
-> JSON.GET doc '$.a[?@ anyof [1,2,3]]'
-"[[1,9]]"
-> JSON.SET doc $ '{"a":[[4,5],[1,9],[]]}'
-OK
-> JSON.GET doc '$.a[?@ noneof [1,2,3]]'
-"[[4,5],[]]"
-```
-
-### `size` (`sizeof`) and `empty`
-
-`left sizeof n` matches when the size of `left` equals the integer `n`, where size is the number of characters in a string, the number of elements in an array, or the number of members in an object. A non-integer `n` is truncated toward zero; a non-numeric `n` never matches. `size` is an alias for `sizeof`.
-
-`left empty true` matches an empty string, array, or object; `left empty false` matches a non-empty one.
-
-```
-> JSON.SET doc $ '{"a":[[4,5],[1],[7,8,9]]}'
-OK
-> JSON.GET doc '$.a[?@ sizeof 2]'
-"[[4,5]]"
-> JSON.SET doc $ '{"a":[[],[1],"",[2,3],{},{"k":1}]}'
-OK
-> JSON.GET doc '$.a[?@ empty true]'
-"[[],\"\",{}]"
-> JSON.GET doc '$.a[?@ empty false]'
-"[[1],[2,3],{\"k\":1}]"
-```
-
-### Get-keys operator `~`
-
-The terminal `~` operator returns the member names of an object as a flat list of strings. Applied to the root (`$~`), it returns the top-level keys. Applied to a multiple-match receiver, it flattens the keys of every matched object. A non-object receiver contributes no keys.
-
-```
-> JSON.SET doc $ '{"obj":{"x":1,"y":2},"books":[{"t":"a"},{"t":"b"}]}'
-OK
-> JSON.GET doc '$.obj~'
-"[\"x\",\"y\"]"
-> JSON.GET doc '$~'
-"[\"obj\",\"books\"]"
-> JSON.GET doc '$.books~'
-"[]"
-```
-
-Because `~` is terminal, expressions such as `$.obj~.x`, `$.obj~~`, and `$.obj.keys()~` are parse errors. For a composable alternative that can be chained with other functions, use the [`keys()`](#keys) function.
-
## Functions
Functions can appear inside filter expressions and, when they return a value, as top-level [projection expressions](#projection-expressions). A function can be written in prefix form, `length($.arr)`, or in postfix (method) form, `$.arr.length()`. A path segment immediately followed by `(` is a method call, so `$.arr.length()` is a function call while `$.arr.length` is a reference to a field named `length`.
@@ -362,39 +239,9 @@ OK
"[{\"t\":\"a\",\"price\":30},{\"t\":\"X\"}]"
```
-## Projection expressions
-
-The top level of a JSONPath query can be an expression that *computes* a value — arithmetic, a function call, a get-keys operator, and so on — rather than only selecting nodes from the document. A query that is a plain path (for example, `$.a.b`, `$..x`, or even a fully parenthesized path such as `($.a)`) still selects nodes; anything else (for example, `$.a + 1`, `-$.a`, `$.arr.length()`, `length($.arr)`, `$.obj~`, or `$.obj.keys()`) is a projection.
-
-A projection that evaluates to *Nothing* produces an empty reply (`[]`).
-
-```
-> JSON.SET doc $ '{"a":2,"b":4,"arr":[1,2,3]}'
-OK
-> JSON.GET doc '$.a + 1'
-"[3]"
-> JSON.GET doc '$.a * $.b'
-"[8]"
-> JSON.GET doc '($.a + $.b) / 2'
-"[3.0]"
-> JSON.GET doc '$.arr.length()'
-"[3]"
-> JSON.GET doc '$.a / 0'
-"[]"
-```
-
-When [`JSON.GET`]({{< relref "commands/json.get/" >}}) is given more than one path, projections and plain paths can be mixed, and each path becomes a key in the returned object:
-
-```
-> JSON.GET doc '$.a + 1' '$.b'
-"{\"$.a + 1\":[3],\"$.b\":[4]}"
-```
-
-[`JSON.MGET`]({{< relref "commands/json.mget/" >}}) evaluates the projection independently for each key. A missing key, or a per-key evaluation error, yields a null reply for that key rather than failing the whole request.
-
-## Multi-language JSONPath examples
+## JSONPath examples
-The following multi-language JSONPath examples use this JSON document, which stores details about items in a store's inventory:
+The following examples use this JSON document, which stores details about items in a store's inventory:
{{< trimmable head="12" tail="8" >}}
```json
@@ -546,7 +393,128 @@ OK
"[\"Quaoar\",\"Weywot\"]"
{{< /clients-example >}}
-See [Filter expression operators](#filter-expression-operators) for more information.
+Beginning with Redis 8.10, the JSONPath filter syntax supports the following operators.
+
+#### Negation `!`
+
+The `!` operator negates a filter condition. It binds more tightly than the logical `&&` and `||` operators, so use parentheses to negate a compound condition. A double negation (`!!`) is equivalent to an existence test.
+
+{{< clients-example set="json_path_ops" step="filter_negation" description="Filter negation: Use the ! operator to invert a filter condition when you need to select elements that do not match" difficulty="advanced" >}}
+> JSON.SET doc $ '[{"a":1,"b":1},{"b":2},{"a":1},{"c":3}]'
+OK
+> JSON.GET doc '$[?!@.a]'
+"[{\"b\":2},{\"c\":3}]"
+> JSON.GET doc '$[?!(@.a==1)]'
+"[{\"b\":2},{\"c\":3}]"
+> JSON.GET doc '$[?!@.a && @.b]'
+"[{\"b\":2}]"
+{{< /clients-example >}}
+
+#### Comparing array and object literals
+
+The `==` and `!=` operators can compare any JSON literal, including array and object literals, on either side of the operator. Comparison is by structural (deep) equality. A comparison between values of different types is always false.
+
+{{< clients-example set="json_path_ops" step="filter_literal_eq" description="Literal comparison: Use == and != to compare array and object literals by structural equality when you need to match a whole nested value" difficulty="advanced" >}}
+> JSON.SET doc $ '{"arrs":[[1],[2],[1,2],[1,[2]]],"objs":[{"x":1},{"x":2},{"y":1}]}'
+OK
+> JSON.GET doc '$.arrs[?(@ == [1])]'
+"[[1]]"
+> JSON.GET doc '$.arrs[?(@ == [1,[2]])]'
+"[[1,[2]]]"
+> JSON.GET doc '$.objs[?(@ == {"x":1})]'
+"[{\"x\":1}]"
+{{< /clients-example >}}
+
+#### Arithmetic operators
+
+The binary operators `+`, `-`, `*`, `/`, and `%` and the unary operators `-` and `+` operate on numbers. Precedence, from highest to lowest, is: unary `-`/`+`, then `*`/`/`/`%`, then `+`/`-`. Use parentheses to override precedence. Division always produces a floating-point result. Division or modulo by zero produces *Nothing*, so no element matches.
+
+Remember that arithmetic operators must be surrounded by spaces (see [Evaluation semantics](#evaluation-semantics)).
+
+{{< clients-example set="json_path_ops" step="filter_arithmetic" description="Arithmetic in filters: Use +, -, *, / and % inside a filter condition when you need to compute a value before comparing it" difficulty="advanced" >}}
+> JSON.SET doc $ '[{"a":2,"b":3},{"a":5,"b":2}]'
+OK
+> JSON.GET doc '$[?@.a + 1 == 3]'
+"[{\"a\":2,\"b\":3}]"
+> JSON.GET doc '$[?@.a + @.b * 2 == 8]'
+"[{\"a\":2,\"b\":3}]"
+> JSON.GET doc '$[?(@.a + @.b) * 2 == 10]'
+"[{\"a\":2,\"b\":3}]"
+{{< /clients-example >}}
+
+#### Membership: `in` and `nin`
+
+`value in array` matches when `value` is a member of `array`; `value nin array` is its strict negation. The right-hand side can be an array literal or a path to an array. Membership uses the same structural comparison and number coercion as `==`. If the right-hand side is not an array, `in` is false (so `nin` is true).
+
+{{< clients-example set="json_path_ops" step="filter_membership" description="Membership filters: Use in and nin to test whether a value is a member of an array when you need set-style matching" difficulty="advanced" >}}
+> JSON.SET doc $ '{"a":[1,2,3,4],"allow":[2,3]}'
+OK
+> JSON.GET doc '$.a[?@ in [2,4]]'
+"[2,4]"
+> JSON.GET doc '$.a[?@ nin [2,4]]'
+"[1,3]"
+> JSON.GET doc '$.a[?@ in $.allow]'
+"[2,3]"
+{{< /clients-example >}}
+
+#### Set relations: `subsetof`, `anyof`, and `noneof`
+
+These operators compare two arrays (each side can be an array literal or a path to an array):
+
+- `subsetof` matches when every element of the left array is a member of the right array. An empty array is a subset of any array.
+- `anyof` matches when the two arrays have a non-empty intersection. An empty array never matches.
+- `noneof` matches when the two arrays have an empty intersection. An empty array always matches.
+
+{{< clients-example set="json_path_ops" step="filter_set_relations" description="Set relation filters: Use subsetof, anyof, and noneof to compare two arrays when you need to match based on their overlap" difficulty="advanced" >}}
+> JSON.SET doc $ '{"a":[[1,2],[1,5],[]]}'
+OK
+> JSON.GET doc '$.a[?@ subsetof [1,2,3]]'
+"[[1,2],[]]"
+> JSON.SET doc $ '{"a":[[1,9],[8,9],[]]}'
+OK
+> JSON.GET doc '$.a[?@ anyof [1,2,3]]'
+"[[1,9]]"
+> JSON.SET doc $ '{"a":[[4,5],[1,9],[]]}'
+OK
+> JSON.GET doc '$.a[?@ noneof [1,2,3]]'
+"[[4,5],[]]"
+{{< /clients-example >}}
+
+#### `size` (`sizeof`) and `empty`
+
+`left sizeof n` matches when the size of `left` equals the integer `n`, where size is the number of characters in a string, the number of elements in an array, or the number of members in an object. A non-integer `n` is truncated toward zero; a non-numeric `n` never matches. `size` is an alias for `sizeof`.
+
+`left empty true` matches an empty string, array, or object; `left empty false` matches a non-empty one.
+
+{{< clients-example set="json_path_ops" step="filter_size_empty" description="Size and emptiness filters: Use sizeof and empty to filter by the size or emptiness of a string, array, or object" difficulty="advanced" >}}
+> JSON.SET doc $ '{"a":[[4,5],[1],[7,8,9]]}'
+OK
+> JSON.GET doc '$.a[?@ sizeof 2]'
+"[[4,5]]"
+> JSON.SET doc $ '{"a":[[],[1],"",[2,3],{},{"k":1}]}'
+OK
+> JSON.GET doc '$.a[?@ empty true]'
+"[[],\"\",{}]"
+> JSON.GET doc '$.a[?@ empty false]'
+"[[1],[2,3],{\"k\":1}]"
+{{< /clients-example >}}
+
+#### Get-keys operator `~`
+
+The terminal `~` operator returns the member names of an object as a flat list of strings. Applied to the root (`$~`), it returns the top-level keys. Applied to a multiple-match receiver, it flattens the keys of every matched object. A non-object receiver contributes no keys.
+
+Because `~` is terminal, expressions such as `$.obj~.x`, `$.obj~~`, and `$.obj.keys()~` are parse errors. For a composable alternative that can be chained with other functions, use the [`keys()`](#keys) function.
+
+{{< clients-example set="json_path_ops" step="filter_getkeys" description="Get-keys filter: Use the ~ operator to retrieve an object's member names as a list of strings" difficulty="advanced" >}}
+> JSON.SET doc $ '{"obj":{"x":1,"y":2},"books":[{"t":"a"},{"t":"b"}]}'
+OK
+> JSON.GET doc '$.obj~'
+"[\"x\",\"y\"]"
+> JSON.GET doc '$~'
+"[\"obj\",\"books\"]"
+> JSON.GET doc '$.books~'
+"[]"
+{{< /clients-example >}}
### Update examples
@@ -582,6 +550,36 @@ JSONPath queries also work with other JSON commands that accept a path as an arg
"[[\"black\",\"silver\",\"pink\"],[\"black\",\"white\"],[\"black\",\"silver\",\"pink\"]]"
{{< /clients-example >}}
+## Projection expressions
+
+The top level of a JSONPath query can be an expression that *computes* a value — arithmetic, a function call, a get-keys operator, and so on — rather than only selecting nodes from the document. A query that is a plain path (for example, `$.a.b`, `$..x`, or even a fully parenthesized path such as `($.a)`) still selects nodes; anything else (for example, `$.a + 1`, `-$.a`, `$.arr.length()`, `length($.arr)`, `$.obj~`, or `$.obj.keys()`) is a projection.
+
+A projection that evaluates to *Nothing* produces an empty reply (`[]`).
+
+```
+> JSON.SET doc $ '{"a":2,"b":4,"arr":[1,2,3]}'
+OK
+> JSON.GET doc '$.a + 1'
+"[3]"
+> JSON.GET doc '$.a * $.b'
+"[8]"
+> JSON.GET doc '($.a + $.b) / 2'
+"[3.0]"
+> JSON.GET doc '$.arr.length()'
+"[3]"
+> JSON.GET doc '$.a / 0'
+"[]"
+```
+
+When [`JSON.GET`]({{< relref "commands/json.get/" >}}) is given more than one path, projections and plain paths can be mixed, and each path becomes a key in the returned object:
+
+```
+> JSON.GET doc '$.a + 1' '$.b'
+"{\"$.a + 1\":[3],\"$.b\":[4]}"
+```
+
+[`JSON.MGET`]({{< relref "commands/json.mget/" >}}) evaluates the projection independently for each key. A missing key, or a per-key evaluation error, yields a null reply for that key rather than failing the whole request.
+
## Legacy path syntax
RedisJSON v1 had the following path implementation. JSON v2 still supports this legacy path in addition to JSONPath.
diff --git a/local_examples/json_path_ops/go-redis/json_path_ops_test.go b/local_examples/json_path_ops/go-redis/json_path_ops_test.go
new file mode 100644
index 0000000000..658d67f1e6
--- /dev/null
+++ b/local_examples/json_path_ops/go-redis/json_path_ops_test.go
@@ -0,0 +1,444 @@
+// EXAMPLE: json_path_ops
+// HIDE_START
+package example_commands_test
+
+import (
+ "context"
+ "fmt"
+
+ "github.com/redis/go-redis/v9"
+)
+
+// HIDE_END
+
+func ExampleClient_json_path_ops_filter_negation() {
+ ctx := context.Background()
+
+ rdb := redis.NewClient(&redis.Options{
+ Addr: "localhost:6379",
+ Password: "", // no password docs
+ DB: 0, // use default DB
+ })
+
+ // REMOVE_START
+ rdb.FlushDB(ctx)
+ rdb.Del(ctx, "doc")
+ // REMOVE_END
+
+ // STEP_START filter_negation
+ res1, err := rdb.JSONSet(ctx, "doc", "$",
+ `[{"a":1,"b":1},{"b":2},{"a":1},{"c":3}]`,
+ ).Result()
+
+ if err != nil {
+ panic(err)
+ }
+
+ fmt.Println(res1) // >>> OK
+
+ res2, err := rdb.JSONGet(ctx, "doc", `$[?!@.a]`).Result()
+
+ if err != nil {
+ panic(err)
+ }
+
+ fmt.Println(res2) // >>> [{"b":2},{"c":3}]
+
+ res3, err := rdb.JSONGet(ctx, "doc", `$[?!(@.a==1)]`).Result()
+
+ if err != nil {
+ panic(err)
+ }
+
+ fmt.Println(res3) // >>> [{"b":2},{"c":3}]
+
+ res4, err := rdb.JSONGet(ctx, "doc", `$[?!@.a && @.b]`).Result()
+
+ if err != nil {
+ panic(err)
+ }
+
+ fmt.Println(res4) // >>> [{"b":2}]
+ // STEP_END
+
+ // Output:
+ // OK
+ // [{"b":2},{"c":3}]
+ // [{"b":2},{"c":3}]
+ // [{"b":2}]
+}
+
+func ExampleClient_json_path_ops_filter_literal_eq() {
+ ctx := context.Background()
+
+ rdb := redis.NewClient(&redis.Options{
+ Addr: "localhost:6379",
+ Password: "", // no password docs
+ DB: 0, // use default DB
+ })
+
+ // REMOVE_START
+ rdb.FlushDB(ctx)
+ rdb.Del(ctx, "doc")
+ // REMOVE_END
+
+ // STEP_START filter_literal_eq
+ res1, err := rdb.JSONSet(ctx, "doc", "$",
+ `{"arrs":[[1],[2],[1,2],[1,[2]]],"objs":[{"x":1},{"x":2},{"y":1}]}`,
+ ).Result()
+
+ if err != nil {
+ panic(err)
+ }
+
+ fmt.Println(res1) // >>> OK
+
+ res2, err := rdb.JSONGet(ctx, "doc", `$.arrs[?(@ == [1])]`).Result()
+
+ if err != nil {
+ panic(err)
+ }
+
+ fmt.Println(res2) // >>> [[1]]
+
+ res3, err := rdb.JSONGet(ctx, "doc", `$.arrs[?(@ == [1,[2]])]`).Result()
+
+ if err != nil {
+ panic(err)
+ }
+
+ fmt.Println(res3) // >>> [[1,[2]]]
+
+ res4, err := rdb.JSONGet(ctx, "doc", `$.objs[?(@ == {"x":1})]`).Result()
+
+ if err != nil {
+ panic(err)
+ }
+
+ fmt.Println(res4) // >>> [{"x":1}]
+ // STEP_END
+
+ // Output:
+ // OK
+ // [[1]]
+ // [[1,[2]]]
+ // [{"x":1}]
+}
+
+func ExampleClient_json_path_ops_filter_arithmetic() {
+ ctx := context.Background()
+
+ rdb := redis.NewClient(&redis.Options{
+ Addr: "localhost:6379",
+ Password: "", // no password docs
+ DB: 0, // use default DB
+ })
+
+ // REMOVE_START
+ rdb.FlushDB(ctx)
+ rdb.Del(ctx, "doc")
+ // REMOVE_END
+
+ // STEP_START filter_arithmetic
+ res1, err := rdb.JSONSet(ctx, "doc", "$",
+ `[{"a":2,"b":3},{"a":5,"b":2}]`,
+ ).Result()
+
+ if err != nil {
+ panic(err)
+ }
+
+ fmt.Println(res1) // >>> OK
+
+ res2, err := rdb.JSONGet(ctx, "doc", `$[?@.a + 1 == 3]`).Result()
+
+ if err != nil {
+ panic(err)
+ }
+
+ fmt.Println(res2) // >>> [{"a":2,"b":3}]
+
+ res3, err := rdb.JSONGet(ctx, "doc", `$[?@.a + @.b * 2 == 8]`).Result()
+
+ if err != nil {
+ panic(err)
+ }
+
+ fmt.Println(res3) // >>> [{"a":2,"b":3}]
+
+ res4, err := rdb.JSONGet(ctx, "doc", `$[?(@.a + @.b) * 2 == 10]`).Result()
+
+ if err != nil {
+ panic(err)
+ }
+
+ fmt.Println(res4) // >>> [{"a":2,"b":3}]
+ // STEP_END
+
+ // Output:
+ // OK
+ // [{"a":2,"b":3}]
+ // [{"a":2,"b":3}]
+ // [{"a":2,"b":3}]
+}
+
+func ExampleClient_json_path_ops_filter_membership() {
+ ctx := context.Background()
+
+ rdb := redis.NewClient(&redis.Options{
+ Addr: "localhost:6379",
+ Password: "", // no password docs
+ DB: 0, // use default DB
+ })
+
+ // REMOVE_START
+ rdb.FlushDB(ctx)
+ rdb.Del(ctx, "doc")
+ // REMOVE_END
+
+ // STEP_START filter_membership
+ res1, err := rdb.JSONSet(ctx, "doc", "$",
+ `{"a":[1,2,3,4],"allow":[2,3]}`,
+ ).Result()
+
+ if err != nil {
+ panic(err)
+ }
+
+ fmt.Println(res1) // >>> OK
+
+ res2, err := rdb.JSONGet(ctx, "doc", `$.a[?@ in [2,4]]`).Result()
+
+ if err != nil {
+ panic(err)
+ }
+
+ fmt.Println(res2) // >>> [2,4]
+
+ res3, err := rdb.JSONGet(ctx, "doc", `$.a[?@ nin [2,4]]`).Result()
+
+ if err != nil {
+ panic(err)
+ }
+
+ fmt.Println(res3) // >>> [1,3]
+
+ res4, err := rdb.JSONGet(ctx, "doc", `$.a[?@ in $.allow]`).Result()
+
+ if err != nil {
+ panic(err)
+ }
+
+ fmt.Println(res4) // >>> [2,3]
+ // STEP_END
+
+ // Output:
+ // OK
+ // [2,4]
+ // [1,3]
+ // [2,3]
+}
+
+func ExampleClient_json_path_ops_filter_set_relations() {
+ ctx := context.Background()
+
+ rdb := redis.NewClient(&redis.Options{
+ Addr: "localhost:6379",
+ Password: "", // no password docs
+ DB: 0, // use default DB
+ })
+
+ // REMOVE_START
+ rdb.FlushDB(ctx)
+ rdb.Del(ctx, "doc")
+ // REMOVE_END
+
+ // STEP_START filter_set_relations
+ res1, err := rdb.JSONSet(ctx, "doc", "$",
+ `{"a":[[1,2],[1,5],[]]}`,
+ ).Result()
+
+ if err != nil {
+ panic(err)
+ }
+
+ fmt.Println(res1) // >>> OK
+
+ res2, err := rdb.JSONGet(ctx, "doc", `$.a[?@ subsetof [1,2,3]]`).Result()
+
+ if err != nil {
+ panic(err)
+ }
+
+ fmt.Println(res2) // >>> [[1,2],[]]
+
+ res3, err := rdb.JSONSet(ctx, "doc", "$",
+ `{"a":[[1,9],[8,9],[]]}`,
+ ).Result()
+
+ if err != nil {
+ panic(err)
+ }
+
+ fmt.Println(res3) // >>> OK
+
+ res4, err := rdb.JSONGet(ctx, "doc", `$.a[?@ anyof [1,2,3]]`).Result()
+
+ if err != nil {
+ panic(err)
+ }
+
+ fmt.Println(res4) // >>> [[1,9]]
+
+ res5, err := rdb.JSONSet(ctx, "doc", "$",
+ `{"a":[[4,5],[1,9],[]]}`,
+ ).Result()
+
+ if err != nil {
+ panic(err)
+ }
+
+ fmt.Println(res5) // >>> OK
+
+ res6, err := rdb.JSONGet(ctx, "doc", `$.a[?@ noneof [1,2,3]]`).Result()
+
+ if err != nil {
+ panic(err)
+ }
+
+ fmt.Println(res6) // >>> [[4,5],[]]
+ // STEP_END
+
+ // Output:
+ // OK
+ // [[1,2],[]]
+ // OK
+ // [[1,9]]
+ // OK
+ // [[4,5],[]]
+}
+
+func ExampleClient_json_path_ops_filter_size_empty() {
+ ctx := context.Background()
+
+ rdb := redis.NewClient(&redis.Options{
+ Addr: "localhost:6379",
+ Password: "", // no password docs
+ DB: 0, // use default DB
+ })
+
+ // REMOVE_START
+ rdb.FlushDB(ctx)
+ rdb.Del(ctx, "doc")
+ // REMOVE_END
+
+ // STEP_START filter_size_empty
+ res1, err := rdb.JSONSet(ctx, "doc", "$",
+ `{"a":[[4,5],[1],[7,8,9]]}`,
+ ).Result()
+
+ if err != nil {
+ panic(err)
+ }
+
+ fmt.Println(res1) // >>> OK
+
+ res2, err := rdb.JSONGet(ctx, "doc", `$.a[?@ sizeof 2]`).Result()
+
+ if err != nil {
+ panic(err)
+ }
+
+ fmt.Println(res2) // >>> [[4,5]]
+
+ res3, err := rdb.JSONSet(ctx, "doc", "$",
+ `{"a":[[],[1],"",[2,3],{},{"k":1}]}`,
+ ).Result()
+
+ if err != nil {
+ panic(err)
+ }
+
+ fmt.Println(res3) // >>> OK
+
+ res4, err := rdb.JSONGet(ctx, "doc", `$.a[?@ empty true]`).Result()
+
+ if err != nil {
+ panic(err)
+ }
+
+ fmt.Println(res4) // >>> [[],"",{}]
+
+ res5, err := rdb.JSONGet(ctx, "doc", `$.a[?@ empty false]`).Result()
+
+ if err != nil {
+ panic(err)
+ }
+
+ fmt.Println(res5) // >>> [[1],[2,3],{"k":1}]
+ // STEP_END
+
+ // Output:
+ // OK
+ // [[4,5]]
+ // OK
+ // [[],"",{}]
+ // [[1],[2,3],{"k":1}]
+}
+
+func ExampleClient_json_path_ops_filter_getkeys() {
+ ctx := context.Background()
+
+ rdb := redis.NewClient(&redis.Options{
+ Addr: "localhost:6379",
+ Password: "", // no password docs
+ DB: 0, // use default DB
+ })
+
+ // REMOVE_START
+ rdb.FlushDB(ctx)
+ rdb.Del(ctx, "doc")
+ // REMOVE_END
+
+ // STEP_START filter_getkeys
+ res1, err := rdb.JSONSet(ctx, "doc", "$",
+ `{"obj":{"x":1,"y":2},"books":[{"t":"a"},{"t":"b"}]}`,
+ ).Result()
+
+ if err != nil {
+ panic(err)
+ }
+
+ fmt.Println(res1) // >>> OK
+
+ res2, err := rdb.JSONGet(ctx, "doc", `$.obj~`).Result()
+
+ if err != nil {
+ panic(err)
+ }
+
+ fmt.Println(res2) // >>> ["x","y"]
+
+ res3, err := rdb.JSONGet(ctx, "doc", `$~`).Result()
+
+ if err != nil {
+ panic(err)
+ }
+
+ fmt.Println(res3) // >>> ["obj","books"]
+
+ res4, err := rdb.JSONGet(ctx, "doc", `$.books~`).Result()
+
+ if err != nil {
+ panic(err)
+ }
+
+ fmt.Println(res4) // >>> []
+ // STEP_END
+
+ // Output:
+ // OK
+ // ["x","y"]
+ // ["obj","books"]
+ // []
+}
diff --git a/local_examples/json_path_ops/jedis/JsonPathOpsExample.java b/local_examples/json_path_ops/jedis/JsonPathOpsExample.java
new file mode 100644
index 0000000000..d99c364d81
--- /dev/null
+++ b/local_examples/json_path_ops/jedis/JsonPathOpsExample.java
@@ -0,0 +1,192 @@
+// EXAMPLE: json_path_ops
+// REMOVE_START
+package io.redis.examples;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+// REMOVE_END
+
+// HIDE_START
+import redis.clients.jedis.RedisClient;
+import redis.clients.jedis.json.Path;
+import redis.clients.jedis.json.Path2;
+import org.json.JSONObject;
+// HIDE_END
+
+// HIDE_START
+public class JsonPathOpsExample {
+
+ @Test
+ public void run() {
+ RedisClient jedis = RedisClient.create("redis://localhost:6379");
+
+ // REMOVE_START
+ jedis.del("doc");
+ // REMOVE_END
+// HIDE_END
+
+ // STEP_START filter_negation
+ String res1 = jedis.jsonSet("doc", new Path2("$"), "[{\"a\":1,\"b\":1},{\"b\":2},{\"a\":1},{\"c\":3}]");
+ System.out.println(res1); // >>> OK
+
+ Object res2 = jedis.jsonGet("doc", new Path2("$[?!@.a]"));
+ System.out.println(res2); // >>> [{"b":2},{"c":3}]
+
+ Object res3 = jedis.jsonGet("doc", new Path2("$[?!(@.a==1)]"));
+ System.out.println(res3); // >>> [{"b":2},{"c":3}]
+
+ Object res4 = jedis.jsonGet("doc", new Path2("$[?!@.a && @.b]"));
+ System.out.println(res4); // >>> [{"b":2}]
+ // STEP_END
+ // REMOVE_START
+ assertEquals("[{\"b\":2},{\"c\":3}]", res2.toString());
+ assertEquals("[{\"b\":2},{\"c\":3}]", res3.toString());
+ assertEquals("[{\"b\":2}]", res4.toString());
+ jedis.del("doc");
+ // REMOVE_END
+
+ // STEP_START filter_literal_eq
+ String res5 = jedis.jsonSet(
+ "doc",
+ new Path2("$"),
+ "{\"arrs\":[[1],[2],[1,2],[1,[2]]],\"objs\":[{\"x\":1},{\"x\":2},{\"y\":1}]}"
+ );
+ System.out.println(res5); // >>> OK
+
+ Object res6 = jedis.jsonGet("doc", new Path2("$.arrs[?(@ == [1])]"));
+ System.out.println(res6); // >>> [[1]]
+
+ Object res7 = jedis.jsonGet("doc", new Path2("$.arrs[?(@ == [1,[2]])]"));
+ System.out.println(res7); // >>> [[1,[2]]]
+
+ Object res8 = jedis.jsonGet("doc", new Path2("$.objs[?(@ == {\"x\":1})]"));
+ System.out.println(res8); // >>> [{"x":1}]
+ // STEP_END
+ // REMOVE_START
+ assertEquals("[[1]]", res6.toString());
+ assertEquals("[[1,[2]]]", res7.toString());
+ assertEquals("[{\"x\":1}]", res8.toString());
+ jedis.del("doc");
+ // REMOVE_END
+
+ // STEP_START filter_arithmetic
+ String res9 = jedis.jsonSet("doc", new Path2("$"), "[{\"a\":2,\"b\":3},{\"a\":5,\"b\":2}]");
+ System.out.println(res9); // >>> OK
+
+ Object res10 = jedis.jsonGet("doc", new Path2("$[?@.a + 1 == 3]"));
+ System.out.println(res10); // >>> [{"a":2,"b":3}]
+
+ Object res11 = jedis.jsonGet("doc", new Path2("$[?@.a + @.b * 2 == 8]"));
+ System.out.println(res11); // >>> [{"a":2,"b":3}]
+
+ Object res12 = jedis.jsonGet("doc", new Path2("$[?(@.a + @.b) * 2 == 10]"));
+ System.out.println(res12); // >>> [{"a":2,"b":3}]
+ // STEP_END
+ // REMOVE_START
+ assertEquals("[{\"a\":2,\"b\":3}]", res10.toString());
+ assertEquals("[{\"a\":2,\"b\":3}]", res11.toString());
+ assertEquals("[{\"a\":2,\"b\":3}]", res12.toString());
+ jedis.del("doc");
+ // REMOVE_END
+
+ // STEP_START filter_membership
+ String res13 = jedis.jsonSet("doc", new Path2("$"), "{\"a\":[1,2,3,4],\"allow\":[2,3]}");
+ System.out.println(res13); // >>> OK
+
+ Object res14 = jedis.jsonGet("doc", new Path2("$.a[?@ in [2,4]]"));
+ System.out.println(res14); // >>> [2,4]
+
+ Object res15 = jedis.jsonGet("doc", new Path2("$.a[?@ nin [2,4]]"));
+ System.out.println(res15); // >>> [1,3]
+
+ Object res16 = jedis.jsonGet("doc", new Path2("$.a[?@ in $.allow]"));
+ System.out.println(res16); // >>> [2,3]
+ // STEP_END
+ // REMOVE_START
+ assertEquals("[2,4]", res14.toString());
+ assertEquals("[1,3]", res15.toString());
+ assertEquals("[2,3]", res16.toString());
+ jedis.del("doc");
+ // REMOVE_END
+
+ // STEP_START filter_set_relations
+ String res17 = jedis.jsonSet("doc", new Path2("$"), "{\"a\":[[1,2],[1,5],[]]}");
+ System.out.println(res17); // >>> OK
+
+ Object res18 = jedis.jsonGet("doc", new Path2("$.a[?@ subsetof [1,2,3]]"));
+ System.out.println(res18); // >>> [[1,2],[]]
+
+ String res19 = jedis.jsonSet("doc", new Path2("$"), "{\"a\":[[1,9],[8,9],[]]}");
+ System.out.println(res19); // >>> OK
+
+ Object res20 = jedis.jsonGet("doc", new Path2("$.a[?@ anyof [1,2,3]]"));
+ System.out.println(res20); // >>> [[1,9]]
+
+ String res21 = jedis.jsonSet("doc", new Path2("$"), "{\"a\":[[4,5],[1,9],[]]}");
+ System.out.println(res21); // >>> OK
+
+ Object res22 = jedis.jsonGet("doc", new Path2("$.a[?@ noneof [1,2,3]]"));
+ System.out.println(res22); // >>> [[4,5],[]]
+ // STEP_END
+ // REMOVE_START
+ assertEquals("[[1,2],[]]", res18.toString());
+ assertEquals("[[1,9]]", res20.toString());
+ assertEquals("[[4,5],[]]", res22.toString());
+ jedis.del("doc");
+ // REMOVE_END
+
+ // STEP_START filter_size_empty
+ String res23 = jedis.jsonSet("doc", new Path2("$"), "{\"a\":[[4,5],[1],[7,8,9]]}");
+ System.out.println(res23); // >>> OK
+
+ Object res24 = jedis.jsonGet("doc", new Path2("$.a[?@ sizeof 2]"));
+ System.out.println(res24); // >>> [[4,5]]
+
+ String res25 = jedis.jsonSet("doc", new Path2("$"), "{\"a\":[[],[1],\"\",[2,3],{},{\"k\":1}]}");
+ System.out.println(res25); // >>> OK
+
+ Object res26 = jedis.jsonGet("doc", new Path2("$.a[?@ empty true]"));
+ System.out.println(res26); // >>> [[],"",{}]
+
+ Object res27 = jedis.jsonGet("doc", new Path2("$.a[?@ empty false]"));
+ System.out.println(res27); // >>> [[1],[2,3],{"k":1}]
+ // STEP_END
+ // REMOVE_START
+ assertEquals("[[4,5]]", res24.toString());
+ assertEquals("[[],\"\",{}]", res26.toString());
+ assertEquals("[[1],[2,3],{\"k\":1}]", res27.toString());
+ jedis.del("doc");
+ // REMOVE_END
+
+ // STEP_START filter_getkeys
+ String res28 = jedis.jsonSet(
+ "doc",
+ new Path2("$"),
+ "{\"obj\":{\"x\":1,\"y\":2},\"books\":[{\"t\":\"a\"},{\"t\":\"b\"}]}"
+ );
+ System.out.println(res28); // >>> OK
+
+ Object res29 = jedis.jsonGet("doc", new Path2("$.obj~"));
+ System.out.println(res29); // >>> ["x","y"]
+
+ Object res30 = jedis.jsonGet("doc", new Path2("$~"));
+ System.out.println(res30); // >>> ["obj","books"]
+
+ Object res31 = jedis.jsonGet("doc", new Path2("$.books~"));
+ System.out.println(res31); // >>> []
+ // STEP_END
+ // REMOVE_START
+ assertEquals("[\"x\",\"y\"]", res29.toString());
+ assertEquals("[\"obj\",\"books\"]", res30.toString());
+ assertEquals("[]", res31.toString());
+ jedis.del("doc");
+ // REMOVE_END
+
+
+// HIDE_START
+ jedis.close();
+ }
+}
+// HIDE_END
diff --git a/local_examples/json_path_ops/lettuce-async/JsonPathOpsExample.java b/local_examples/json_path_ops/lettuce-async/JsonPathOpsExample.java
new file mode 100644
index 0000000000..e6655c35f3
--- /dev/null
+++ b/local_examples/json_path_ops/lettuce-async/JsonPathOpsExample.java
@@ -0,0 +1,307 @@
+// EXAMPLE: json_path_ops
+package io.redis.examples.async;
+
+import io.lettuce.core.*;
+import io.lettuce.core.api.async.RedisAsyncCommands;
+import io.lettuce.core.api.StatefulRedisConnection;
+import io.lettuce.core.json.JsonPath;
+import io.lettuce.core.json.JsonParser;
+import io.lettuce.core.json.JsonObject;
+
+import java.util.concurrent.CompletableFuture;
+// REMOVE_START
+import org.junit.jupiter.api.Test;
+import static org.assertj.core.api.Assertions.assertThat;
+// REMOVE_END
+
+public class JsonPathOpsExample {
+
+ // REMOVE_START
+ @Test
+ // REMOVE_END
+ public void run() {
+ // REMOVE_START
+ RedisClient redisClient = RedisClient.create("redis://localhost:6379");
+
+ try (StatefulRedisConnection connection = redisClient.connect()) {
+ RedisAsyncCommands asyncCommands = connection.async();
+ // REMOVE_END
+
+ JsonParser parser = asyncCommands.getJsonParser();
+
+ // REMOVE_START
+ asyncCommands.del("doc").toCompletableFuture().join();
+ // REMOVE_END
+
+ // STEP_START filter_negation
+ CompletableFuture filterNegationExample = asyncCommands
+ .jsonSet("doc", JsonPath.ROOT_PATH,
+ parser.createJsonValue("[{\"a\":1,\"b\":1},{\"b\":2},{\"a\":1},{\"c\":3}]"))
+ .thenCompose(res1 -> {
+ System.out.println(res1); // >>> OK
+ // REMOVE_START
+ assertThat(res1).isEqualTo("OK");
+ // REMOVE_END
+ return asyncCommands.jsonGet("doc", JsonPath.of("$[?!@.a]"));
+ }).thenCompose(res2 -> {
+ System.out.println(res2); // >>> [[{"b":2},{"c":3}]]
+ // REMOVE_START
+ assertThat(res2.toString()).isEqualTo("[[{\"b\":2},{\"c\":3}]]");
+ // REMOVE_END
+ return asyncCommands.jsonGet("doc", JsonPath.of("$[?!(@.a==1)]"));
+ }).thenCompose(res3 -> {
+ System.out.println(res3); // >>> [[{"b":2},{"c":3}]]
+ // REMOVE_START
+ assertThat(res3.toString()).isEqualTo("[[{\"b\":2},{\"c\":3}]]");
+ // REMOVE_END
+ return asyncCommands.jsonGet("doc", JsonPath.of("$[?!@.a && @.b]"));
+ }).thenAccept(res4 -> {
+ System.out.println(res4); // >>> [[{"b":2}]]
+ // REMOVE_START
+ assertThat(res4.toString()).isEqualTo("[[{\"b\":2}]]");
+ // REMOVE_END
+ }).toCompletableFuture();
+ // STEP_END
+
+ filterNegationExample.join();
+ // REMOVE_START
+ asyncCommands.del("doc").toCompletableFuture().join();
+ // REMOVE_END
+
+ // STEP_START filter_literal_eq
+ CompletableFuture filterLiteralEqExample = asyncCommands
+ .jsonSet("doc", JsonPath.ROOT_PATH,
+ parser.createJsonValue(
+ "{\"arrs\":[[1],[2],[1,2],[1,[2]]],\"objs\":[{\"x\":1},{\"x\":2},{\"y\":1}]}"))
+ .thenCompose(res1 -> {
+ System.out.println(res1); // >>> OK
+ // REMOVE_START
+ assertThat(res1).isEqualTo("OK");
+ // REMOVE_END
+ return asyncCommands.jsonGet("doc", JsonPath.of("$.arrs[?(@ == [1])]"));
+ }).thenCompose(res2 -> {
+ System.out.println(res2); // >>> [[[1]]]
+ // REMOVE_START
+ assertThat(res2.toString()).isEqualTo("[[[1]]]");
+ // REMOVE_END
+ return asyncCommands.jsonGet("doc", JsonPath.of("$.arrs[?(@ == [1,[2]])]"));
+ }).thenCompose(res3 -> {
+ System.out.println(res3); // >>> [[[1,[2]]]]
+ // REMOVE_START
+ assertThat(res3.toString()).isEqualTo("[[[1,[2]]]]");
+ // REMOVE_END
+ return asyncCommands.jsonGet("doc", JsonPath.of("$.objs[?(@ == {\"x\":1})]"));
+ }).thenAccept(res4 -> {
+ System.out.println(res4); // >>> [[{"x":1}]]
+ // REMOVE_START
+ assertThat(res4.toString()).isEqualTo("[[{\"x\":1}]]");
+ // REMOVE_END
+ }).toCompletableFuture();
+ // STEP_END
+
+ filterLiteralEqExample.join();
+ // REMOVE_START
+ asyncCommands.del("doc").toCompletableFuture().join();
+ // REMOVE_END
+
+ // STEP_START filter_arithmetic
+ CompletableFuture filterArithmeticExample = asyncCommands
+ .jsonSet("doc", JsonPath.ROOT_PATH,
+ parser.createJsonValue("[{\"a\":2,\"b\":3},{\"a\":5,\"b\":2}]"))
+ .thenCompose(res1 -> {
+ System.out.println(res1); // >>> OK
+ // REMOVE_START
+ assertThat(res1).isEqualTo("OK");
+ // REMOVE_END
+ return asyncCommands.jsonGet("doc", JsonPath.of("$[?@.a + 1 == 3]"));
+ }).thenCompose(res2 -> {
+ System.out.println(res2); // >>> [[{"a":2,"b":3}]]
+ // REMOVE_START
+ assertThat(res2.toString()).isEqualTo("[[{\"a\":2,\"b\":3}]]");
+ // REMOVE_END
+ return asyncCommands.jsonGet("doc", JsonPath.of("$[?@.a + @.b * 2 == 8]"));
+ }).thenCompose(res3 -> {
+ System.out.println(res3); // >>> [[{"a":2,"b":3}]]
+ // REMOVE_START
+ assertThat(res3.toString()).isEqualTo("[[{\"a\":2,\"b\":3}]]");
+ // REMOVE_END
+ return asyncCommands.jsonGet("doc", JsonPath.of("$[?(@.a + @.b) * 2 == 10]"));
+ }).thenAccept(res4 -> {
+ System.out.println(res4); // >>> [[{"a":2,"b":3}]]
+ // REMOVE_START
+ assertThat(res4.toString()).isEqualTo("[[{\"a\":2,\"b\":3}]]");
+ // REMOVE_END
+ }).toCompletableFuture();
+ // STEP_END
+
+ filterArithmeticExample.join();
+ // REMOVE_START
+ asyncCommands.del("doc").toCompletableFuture().join();
+ // REMOVE_END
+
+ // STEP_START filter_membership
+ CompletableFuture filterMembershipExample = asyncCommands
+ .jsonSet("doc", JsonPath.ROOT_PATH,
+ parser.createJsonValue("{\"a\":[1,2,3,4],\"allow\":[2,3]}"))
+ .thenCompose(res1 -> {
+ System.out.println(res1); // >>> OK
+ // REMOVE_START
+ assertThat(res1).isEqualTo("OK");
+ // REMOVE_END
+ return asyncCommands.jsonGet("doc", JsonPath.of("$.a[?@ in [2,4]]"));
+ }).thenCompose(res2 -> {
+ System.out.println(res2); // >>> [[2,4]]
+ // REMOVE_START
+ assertThat(res2.toString()).isEqualTo("[[2,4]]");
+ // REMOVE_END
+ return asyncCommands.jsonGet("doc", JsonPath.of("$.a[?@ nin [2,4]]"));
+ }).thenCompose(res3 -> {
+ System.out.println(res3); // >>> [[1,3]]
+ // REMOVE_START
+ assertThat(res3.toString()).isEqualTo("[[1,3]]");
+ // REMOVE_END
+ return asyncCommands.jsonGet("doc", JsonPath.of("$.a[?@ in $.allow]"));
+ }).thenAccept(res4 -> {
+ System.out.println(res4); // >>> [[2,3]]
+ // REMOVE_START
+ assertThat(res4.toString()).isEqualTo("[[2,3]]");
+ // REMOVE_END
+ }).toCompletableFuture();
+ // STEP_END
+
+ filterMembershipExample.join();
+ // REMOVE_START
+ asyncCommands.del("doc").toCompletableFuture().join();
+ // REMOVE_END
+
+ // STEP_START filter_set_relations
+ CompletableFuture filterSetRelationsExample = asyncCommands
+ .jsonSet("doc", JsonPath.ROOT_PATH, parser.createJsonValue("{\"a\":[[1,2],[1,5],[]]}"))
+ .thenCompose(res1 -> {
+ System.out.println(res1); // >>> OK
+ // REMOVE_START
+ assertThat(res1).isEqualTo("OK");
+ // REMOVE_END
+ return asyncCommands.jsonGet("doc", JsonPath.of("$.a[?@ subsetof [1,2,3]]"));
+ }).thenCompose(res2 -> {
+ System.out.println(res2); // >>> [[[1,2],[]]]
+ // REMOVE_START
+ assertThat(res2.toString()).isEqualTo("[[[1,2],[]]]");
+ // REMOVE_END
+ return asyncCommands.jsonSet("doc", JsonPath.ROOT_PATH,
+ parser.createJsonValue("{\"a\":[[1,9],[8,9],[]]}"));
+ }).thenCompose(res3 -> {
+ System.out.println(res3); // >>> OK
+ // REMOVE_START
+ assertThat(res3).isEqualTo("OK");
+ // REMOVE_END
+ return asyncCommands.jsonGet("doc", JsonPath.of("$.a[?@ anyof [1,2,3]]"));
+ }).thenCompose(res4 -> {
+ System.out.println(res4); // >>> [[[1,9]]]
+ // REMOVE_START
+ assertThat(res4.toString()).isEqualTo("[[[1,9]]]");
+ // REMOVE_END
+ return asyncCommands.jsonSet("doc", JsonPath.ROOT_PATH,
+ parser.createJsonValue("{\"a\":[[4,5],[1,9],[]]}"));
+ }).thenCompose(res5 -> {
+ System.out.println(res5); // >>> OK
+ // REMOVE_START
+ assertThat(res5).isEqualTo("OK");
+ // REMOVE_END
+ return asyncCommands.jsonGet("doc", JsonPath.of("$.a[?@ noneof [1,2,3]]"));
+ }).thenAccept(res6 -> {
+ System.out.println(res6); // >>> [[[4,5],[]]]
+ // REMOVE_START
+ assertThat(res6.toString()).isEqualTo("[[[4,5],[]]]");
+ // REMOVE_END
+ }).toCompletableFuture();
+ // STEP_END
+
+ filterSetRelationsExample.join();
+ // REMOVE_START
+ asyncCommands.del("doc").toCompletableFuture().join();
+ // REMOVE_END
+
+ // STEP_START filter_size_empty
+ CompletableFuture filterSizeEmptyExample = asyncCommands
+ .jsonSet("doc", JsonPath.ROOT_PATH, parser.createJsonValue("{\"a\":[[4,5],[1],[7,8,9]]}"))
+ .thenCompose(res1 -> {
+ System.out.println(res1); // >>> OK
+ // REMOVE_START
+ assertThat(res1).isEqualTo("OK");
+ // REMOVE_END
+ return asyncCommands.jsonGet("doc", JsonPath.of("$.a[?@ sizeof 2]"));
+ }).thenCompose(res2 -> {
+ System.out.println(res2); // >>> [[[4,5]]]
+ // REMOVE_START
+ assertThat(res2.toString()).isEqualTo("[[[4,5]]]");
+ // REMOVE_END
+ return asyncCommands.jsonSet("doc", JsonPath.ROOT_PATH,
+ parser.createJsonValue("{\"a\":[[],[1],\"\",[2,3],{},{\"k\":1}]}"));
+ }).thenCompose(res3 -> {
+ System.out.println(res3); // >>> OK
+ // REMOVE_START
+ assertThat(res3).isEqualTo("OK");
+ // REMOVE_END
+ return asyncCommands.jsonGet("doc", JsonPath.of("$.a[?@ empty true]"));
+ }).thenCompose(res4 -> {
+ System.out.println(res4); // >>> [[[],"",{}]]
+ // REMOVE_START
+ assertThat(res4.toString()).isEqualTo("[[[],\"\",{}]]");
+ // REMOVE_END
+ return asyncCommands.jsonGet("doc", JsonPath.of("$.a[?@ empty false]"));
+ }).thenAccept(res5 -> {
+ System.out.println(res5); // >>> [[[1],[2,3],{"k":1}]]
+ // REMOVE_START
+ assertThat(res5.toString()).isEqualTo("[[[1],[2,3],{\"k\":1}]]");
+ // REMOVE_END
+ }).toCompletableFuture();
+ // STEP_END
+
+ filterSizeEmptyExample.join();
+ // REMOVE_START
+ asyncCommands.del("doc").toCompletableFuture().join();
+ // REMOVE_END
+
+ // STEP_START filter_getkeys
+ CompletableFuture filterGetkeysExample = asyncCommands
+ .jsonSet("doc", JsonPath.ROOT_PATH,
+ parser.createJsonValue(
+ "{\"obj\":{\"x\":1,\"y\":2},\"books\":[{\"t\":\"a\"},{\"t\":\"b\"}]}"))
+ .thenCompose(res1 -> {
+ System.out.println(res1); // >>> OK
+ // REMOVE_START
+ assertThat(res1).isEqualTo("OK");
+ // REMOVE_END
+ return asyncCommands.jsonGet("doc", JsonPath.of("$.obj~"));
+ }).thenCompose(res2 -> {
+ System.out.println(res2); // >>> [["x","y"]]
+ // REMOVE_START
+ assertThat(res2.toString()).isEqualTo("[[\"x\",\"y\"]]");
+ // REMOVE_END
+ return asyncCommands.jsonGet("doc", JsonPath.of("$~"));
+ }).thenCompose(res3 -> {
+ System.out.println(res3); // >>> [["obj","books"]]
+ // REMOVE_START
+ assertThat(res3.toString()).isEqualTo("[[\"obj\",\"books\"]]");
+ // REMOVE_END
+ return asyncCommands.jsonGet("doc", JsonPath.of("$.books~"));
+ }).thenAccept(res4 -> {
+ System.out.println(res4); // >>> [[]]
+ // REMOVE_START
+ assertThat(res4.toString()).isEqualTo("[[]]");
+ // REMOVE_END
+ }).toCompletableFuture();
+ // STEP_END
+
+ filterGetkeysExample.join();
+ // REMOVE_START
+ asyncCommands.del("doc").toCompletableFuture().join();
+ // REMOVE_END
+
+ } finally {
+ redisClient.shutdown();
+ }
+ }
+
+}
diff --git a/local_examples/json_path_ops/lettuce-reactive/JsonPathOpsExample.java b/local_examples/json_path_ops/lettuce-reactive/JsonPathOpsExample.java
new file mode 100644
index 0000000000..4c5d74ad52
--- /dev/null
+++ b/local_examples/json_path_ops/lettuce-reactive/JsonPathOpsExample.java
@@ -0,0 +1,333 @@
+// EXAMPLE: json_path_ops
+package io.redis.examples.reactive;
+
+import io.lettuce.core.*;
+import io.lettuce.core.api.reactive.RedisReactiveCommands;
+import io.lettuce.core.api.StatefulRedisConnection;
+import io.lettuce.core.json.JsonPath;
+import io.lettuce.core.json.JsonParser;
+import io.lettuce.core.json.JsonObject;
+// REMOVE_START
+import org.junit.jupiter.api.Test;
+import static org.assertj.core.api.Assertions.assertThat;
+// REMOVE_END
+
+import reactor.core.publisher.Mono;
+
+public class JsonPathOpsExample {
+
+ // REMOVE_START
+ @Test
+ // REMOVE_END
+ public void run() {
+ RedisClient redisClient = RedisClient.create("redis://localhost:6379");
+
+ try (StatefulRedisConnection connection = redisClient.connect()) {
+ RedisReactiveCommands reactiveCommands = connection.reactive();
+
+ JsonParser parser = reactiveCommands.getJsonParser();
+
+ // REMOVE_START
+ reactiveCommands.del("doc").block();
+ // REMOVE_END
+
+ // STEP_START filter_negation
+ Mono filterNegationExample = reactiveCommands
+ .jsonSet("doc", JsonPath.ROOT_PATH,
+ parser.createJsonValue("[{\"a\":1,\"b\":1},{\"b\":2},{\"a\":1},{\"c\":3}]"))
+ .doOnNext(res1 -> {
+ System.out.println(res1); // >>> OK
+ // REMOVE_START
+ assertThat(res1).isEqualTo("OK");
+ // REMOVE_END
+ })
+ .flatMap(res1 -> reactiveCommands.jsonGet("doc", JsonPath.of("$[?!@.a]")).collectList())
+ .doOnNext(res2 -> {
+ System.out.println(res2); // >>> [[{"b":2},{"c":3}]]
+ // REMOVE_START
+ assertThat(res2.toString()).isEqualTo("[[{\"b\":2},{\"c\":3}]]");
+ // REMOVE_END
+ })
+ .flatMap(res2 -> reactiveCommands.jsonGet("doc", JsonPath.of("$[?!(@.a==1)]")).collectList())
+ .doOnNext(res3 -> {
+ System.out.println(res3); // >>> [[{"b":2},{"c":3}]]
+ // REMOVE_START
+ assertThat(res3.toString()).isEqualTo("[[{\"b\":2},{\"c\":3}]]");
+ // REMOVE_END
+ })
+ .flatMap(res3 -> reactiveCommands.jsonGet("doc", JsonPath.of("$[?!@.a && @.b]")).collectList())
+ .doOnNext(res4 -> {
+ System.out.println(res4); // >>> [[{"b":2}]]
+ // REMOVE_START
+ assertThat(res4.toString()).isEqualTo("[[{\"b\":2}]]");
+ // REMOVE_END
+ })
+ .then();
+ // STEP_END
+
+ filterNegationExample.block();
+ // REMOVE_START
+ reactiveCommands.del("doc").block();
+ // REMOVE_END
+
+ // STEP_START filter_literal_eq
+ Mono filterLiteralEqExample = reactiveCommands
+ .jsonSet("doc", JsonPath.ROOT_PATH,
+ parser.createJsonValue(
+ "{\"arrs\":[[1],[2],[1,2],[1,[2]]],\"objs\":[{\"x\":1},{\"x\":2},{\"y\":1}]}"))
+ .doOnNext(res1 -> {
+ System.out.println(res1); // >>> OK
+ // REMOVE_START
+ assertThat(res1).isEqualTo("OK");
+ // REMOVE_END
+ })
+ .flatMap(res1 -> reactiveCommands.jsonGet("doc", JsonPath.of("$.arrs[?(@ == [1])]")).collectList())
+ .doOnNext(res2 -> {
+ System.out.println(res2); // >>> [[[1]]]
+ // REMOVE_START
+ assertThat(res2.toString()).isEqualTo("[[[1]]]");
+ // REMOVE_END
+ })
+ .flatMap(res2 -> reactiveCommands.jsonGet("doc", JsonPath.of("$.arrs[?(@ == [1,[2]])]")).collectList())
+ .doOnNext(res3 -> {
+ System.out.println(res3); // >>> [[[1,[2]]]]
+ // REMOVE_START
+ assertThat(res3.toString()).isEqualTo("[[[1,[2]]]]");
+ // REMOVE_END
+ })
+ .flatMap(res3 -> reactiveCommands.jsonGet("doc", JsonPath.of("$.objs[?(@ == {\"x\":1})]")).collectList())
+ .doOnNext(res4 -> {
+ System.out.println(res4); // >>> [[{"x":1}]]
+ // REMOVE_START
+ assertThat(res4.toString()).isEqualTo("[[{\"x\":1}]]");
+ // REMOVE_END
+ })
+ .then();
+ // STEP_END
+
+ filterLiteralEqExample.block();
+ // REMOVE_START
+ reactiveCommands.del("doc").block();
+ // REMOVE_END
+
+ // STEP_START filter_arithmetic
+ Mono filterArithmeticExample = reactiveCommands
+ .jsonSet("doc", JsonPath.ROOT_PATH, parser.createJsonValue("[{\"a\":2,\"b\":3},{\"a\":5,\"b\":2}]"))
+ .doOnNext(res1 -> {
+ System.out.println(res1); // >>> OK
+ // REMOVE_START
+ assertThat(res1).isEqualTo("OK");
+ // REMOVE_END
+ })
+ .flatMap(res1 -> reactiveCommands.jsonGet("doc", JsonPath.of("$[?@.a + 1 == 3]")).collectList())
+ .doOnNext(res2 -> {
+ System.out.println(res2); // >>> [[{"a":2,"b":3}]]
+ // REMOVE_START
+ assertThat(res2.toString()).isEqualTo("[[{\"a\":2,\"b\":3}]]");
+ // REMOVE_END
+ })
+ .flatMap(res2 -> reactiveCommands.jsonGet("doc", JsonPath.of("$[?@.a + @.b * 2 == 8]")).collectList())
+ .doOnNext(res3 -> {
+ System.out.println(res3); // >>> [[{"a":2,"b":3}]]
+ // REMOVE_START
+ assertThat(res3.toString()).isEqualTo("[[{\"a\":2,\"b\":3}]]");
+ // REMOVE_END
+ })
+ .flatMap(res3 -> reactiveCommands.jsonGet("doc", JsonPath.of("$[?(@.a + @.b) * 2 == 10]")).collectList())
+ .doOnNext(res4 -> {
+ System.out.println(res4); // >>> [[{"a":2,"b":3}]]
+ // REMOVE_START
+ assertThat(res4.toString()).isEqualTo("[[{\"a\":2,\"b\":3}]]");
+ // REMOVE_END
+ })
+ .then();
+ // STEP_END
+
+ filterArithmeticExample.block();
+ // REMOVE_START
+ reactiveCommands.del("doc").block();
+ // REMOVE_END
+
+ // STEP_START filter_membership
+ Mono filterMembershipExample = reactiveCommands
+ .jsonSet("doc", JsonPath.ROOT_PATH, parser.createJsonValue("{\"a\":[1,2,3,4],\"allow\":[2,3]}"))
+ .doOnNext(res1 -> {
+ System.out.println(res1); // >>> OK
+ // REMOVE_START
+ assertThat(res1).isEqualTo("OK");
+ // REMOVE_END
+ })
+ .flatMap(res1 -> reactiveCommands.jsonGet("doc", JsonPath.of("$.a[?@ in [2,4]]")).collectList())
+ .doOnNext(res2 -> {
+ System.out.println(res2); // >>> [[2,4]]
+ // REMOVE_START
+ assertThat(res2.toString()).isEqualTo("[[2,4]]");
+ // REMOVE_END
+ })
+ .flatMap(res2 -> reactiveCommands.jsonGet("doc", JsonPath.of("$.a[?@ nin [2,4]]")).collectList())
+ .doOnNext(res3 -> {
+ System.out.println(res3); // >>> [[1,3]]
+ // REMOVE_START
+ assertThat(res3.toString()).isEqualTo("[[1,3]]");
+ // REMOVE_END
+ })
+ .flatMap(res3 -> reactiveCommands.jsonGet("doc", JsonPath.of("$.a[?@ in $.allow]")).collectList())
+ .doOnNext(res4 -> {
+ System.out.println(res4); // >>> [[2,3]]
+ // REMOVE_START
+ assertThat(res4.toString()).isEqualTo("[[2,3]]");
+ // REMOVE_END
+ })
+ .then();
+ // STEP_END
+
+ filterMembershipExample.block();
+ // REMOVE_START
+ reactiveCommands.del("doc").block();
+ // REMOVE_END
+
+ // STEP_START filter_set_relations
+ Mono filterSetRelationsExample = reactiveCommands
+ .jsonSet("doc", JsonPath.ROOT_PATH, parser.createJsonValue("{\"a\":[[1,2],[1,5],[]]}"))
+ .doOnNext(res1 -> {
+ System.out.println(res1); // >>> OK
+ // REMOVE_START
+ assertThat(res1).isEqualTo("OK");
+ // REMOVE_END
+ })
+ .flatMap(res1 -> reactiveCommands.jsonGet("doc", JsonPath.of("$.a[?@ subsetof [1,2,3]]")).collectList())
+ .doOnNext(res2 -> {
+ System.out.println(res2); // >>> [[[1,2],[]]]
+ // REMOVE_START
+ assertThat(res2.toString()).isEqualTo("[[[1,2],[]]]");
+ // REMOVE_END
+ })
+ .flatMap(res2 -> reactiveCommands.jsonSet("doc", JsonPath.ROOT_PATH,
+ parser.createJsonValue("{\"a\":[[1,9],[8,9],[]]}")))
+ .doOnNext(res3 -> {
+ System.out.println(res3); // >>> OK
+ // REMOVE_START
+ assertThat(res3).isEqualTo("OK");
+ // REMOVE_END
+ })
+ .flatMap(res3 -> reactiveCommands.jsonGet("doc", JsonPath.of("$.a[?@ anyof [1,2,3]]")).collectList())
+ .doOnNext(res4 -> {
+ System.out.println(res4); // >>> [[[1,9]]]
+ // REMOVE_START
+ assertThat(res4.toString()).isEqualTo("[[[1,9]]]");
+ // REMOVE_END
+ })
+ .flatMap(res4 -> reactiveCommands.jsonSet("doc", JsonPath.ROOT_PATH,
+ parser.createJsonValue("{\"a\":[[4,5],[1,9],[]]}")))
+ .doOnNext(res5 -> {
+ System.out.println(res5); // >>> OK
+ // REMOVE_START
+ assertThat(res5).isEqualTo("OK");
+ // REMOVE_END
+ })
+ .flatMap(res5 -> reactiveCommands.jsonGet("doc", JsonPath.of("$.a[?@ noneof [1,2,3]]")).collectList())
+ .doOnNext(res6 -> {
+ System.out.println(res6); // >>> [[[4,5],[]]]
+ // REMOVE_START
+ assertThat(res6.toString()).isEqualTo("[[[4,5],[]]]");
+ // REMOVE_END
+ })
+ .then();
+ // STEP_END
+
+ filterSetRelationsExample.block();
+ // REMOVE_START
+ reactiveCommands.del("doc").block();
+ // REMOVE_END
+
+ // STEP_START filter_size_empty
+ Mono filterSizeEmptyExample = reactiveCommands
+ .jsonSet("doc", JsonPath.ROOT_PATH, parser.createJsonValue("{\"a\":[[4,5],[1],[7,8,9]]}"))
+ .doOnNext(res1 -> {
+ System.out.println(res1); // >>> OK
+ // REMOVE_START
+ assertThat(res1).isEqualTo("OK");
+ // REMOVE_END
+ })
+ .flatMap(res1 -> reactiveCommands.jsonGet("doc", JsonPath.of("$.a[?@ sizeof 2]")).collectList())
+ .doOnNext(res2 -> {
+ System.out.println(res2); // >>> [[[4,5]]]
+ // REMOVE_START
+ assertThat(res2.toString()).isEqualTo("[[[4,5]]]");
+ // REMOVE_END
+ })
+ .flatMap(res2 -> reactiveCommands.jsonSet("doc", JsonPath.ROOT_PATH,
+ parser.createJsonValue("{\"a\":[[],[1],\"\",[2,3],{},{\"k\":1}]}")))
+ .doOnNext(res3 -> {
+ System.out.println(res3); // >>> OK
+ // REMOVE_START
+ assertThat(res3).isEqualTo("OK");
+ // REMOVE_END
+ })
+ .flatMap(res3 -> reactiveCommands.jsonGet("doc", JsonPath.of("$.a[?@ empty true]")).collectList())
+ .doOnNext(res4 -> {
+ System.out.println(res4); // >>> [[[],"",{}]]
+ // REMOVE_START
+ assertThat(res4.toString()).isEqualTo("[[[],\"\",{}]]");
+ // REMOVE_END
+ })
+ .flatMap(res4 -> reactiveCommands.jsonGet("doc", JsonPath.of("$.a[?@ empty false]")).collectList())
+ .doOnNext(res5 -> {
+ System.out.println(res5); // >>> [[[1],[2,3],{"k":1}]]
+ // REMOVE_START
+ assertThat(res5.toString()).isEqualTo("[[[1],[2,3],{\"k\":1}]]");
+ // REMOVE_END
+ })
+ .then();
+ // STEP_END
+
+ filterSizeEmptyExample.block();
+ // REMOVE_START
+ reactiveCommands.del("doc").block();
+ // REMOVE_END
+
+ // STEP_START filter_getkeys
+ Mono filterGetkeysExample = reactiveCommands
+ .jsonSet("doc", JsonPath.ROOT_PATH,
+ parser.createJsonValue("{\"obj\":{\"x\":1,\"y\":2},\"books\":[{\"t\":\"a\"},{\"t\":\"b\"}]}"))
+ .doOnNext(res1 -> {
+ System.out.println(res1); // >>> OK
+ // REMOVE_START
+ assertThat(res1).isEqualTo("OK");
+ // REMOVE_END
+ })
+ .flatMap(res1 -> reactiveCommands.jsonGet("doc", JsonPath.of("$.obj~")).collectList())
+ .doOnNext(res2 -> {
+ System.out.println(res2); // >>> [["x","y"]]
+ // REMOVE_START
+ assertThat(res2.toString()).isEqualTo("[[\"x\",\"y\"]]");
+ // REMOVE_END
+ })
+ .flatMap(res2 -> reactiveCommands.jsonGet("doc", JsonPath.of("$~")).collectList())
+ .doOnNext(res3 -> {
+ System.out.println(res3); // >>> [["obj","books"]]
+ // REMOVE_START
+ assertThat(res3.toString()).isEqualTo("[[\"obj\",\"books\"]]");
+ // REMOVE_END
+ })
+ .flatMap(res3 -> reactiveCommands.jsonGet("doc", JsonPath.of("$.books~")).collectList())
+ .doOnNext(res4 -> {
+ System.out.println(res4); // >>> [[]]
+ // REMOVE_START
+ assertThat(res4.toString()).isEqualTo("[[]]");
+ // REMOVE_END
+ })
+ .then();
+ // STEP_END
+
+ filterGetkeysExample.block();
+ // REMOVE_START
+ reactiveCommands.del("doc").block();
+ // REMOVE_END
+
+ } finally {
+ redisClient.shutdown();
+ }
+ }
+
+}
diff --git a/local_examples/json_path_ops/node-redis/json-path-ops.js b/local_examples/json_path_ops/node-redis/json-path-ops.js
new file mode 100644
index 0000000000..30702f9c39
--- /dev/null
+++ b/local_examples/json_path_ops/node-redis/json-path-ops.js
@@ -0,0 +1,179 @@
+// EXAMPLE: json_path_ops
+// HIDE_START
+import assert from 'node:assert';
+import { createClient } from 'redis';
+
+const client = createClient();
+await client.connect().catch(console.error);
+// HIDE_END
+
+// REMOVE_START
+await client.del('doc');
+// REMOVE_END
+
+// STEP_START filter_negation
+const res1 = await client.json.set('doc', '$', [{ a: 1, b: 1 }, { b: 2 }, { a: 1 }, { c: 3 }]);
+console.log(res1); // >>> OK
+
+const res2 = await client.json.get('doc', { path: '$[?!@.a]' });
+console.log(res2); // >>> [ { b: 2 }, { c: 3 } ]
+
+const res3 = await client.json.get('doc', { path: '$[?!(@.a==1)]' });
+console.log(res3); // >>> [ { b: 2 }, { c: 3 } ]
+
+const res4 = await client.json.get('doc', { path: '$[?!@.a && @.b]' });
+console.log(res4); // >>> [ { b: 2 } ]
+// STEP_END
+
+// REMOVE_START
+assert.deepEqual(res2, [{ b: 2 }, { c: 3 }]);
+assert.deepEqual(res3, [{ b: 2 }, { c: 3 }]);
+assert.deepEqual(res4, [{ b: 2 }]);
+await client.del('doc');
+// REMOVE_END
+
+// STEP_START filter_literal_eq
+const res5 = await client.json.set('doc', '$', {
+ arrs: [[1], [2], [1, 2], [1, [2]]],
+ objs: [{ x: 1 }, { x: 2 }, { y: 1 }]
+});
+console.log(res5); // >>> OK
+
+const res6 = await client.json.get('doc', { path: '$.arrs[?(@ == [1])]' });
+console.log(res6); // >>> [ [ 1 ] ]
+
+const res7 = await client.json.get('doc', { path: '$.arrs[?(@ == [1,[2]])]' });
+console.log(res7); // >>> [ [ 1, [ 2 ] ] ]
+
+const res8 = await client.json.get('doc', { path: '$.objs[?(@ == {"x":1})]' });
+console.log(res8); // >>> [ { x: 1 } ]
+// STEP_END
+
+// REMOVE_START
+assert.deepEqual(res6, [[1]]);
+assert.deepEqual(res7, [[1, [2]]]);
+assert.deepEqual(res8, [{ x: 1 }]);
+await client.del('doc');
+// REMOVE_END
+
+// STEP_START filter_arithmetic
+const res9 = await client.json.set('doc', '$', [{ a: 2, b: 3 }, { a: 5, b: 2 }]);
+console.log(res9); // >>> OK
+
+const res10 = await client.json.get('doc', { path: '$[?@.a + 1 == 3]' });
+console.log(res10); // >>> [ { a: 2, b: 3 } ]
+
+const res11 = await client.json.get('doc', { path: '$[?@.a + @.b * 2 == 8]' });
+console.log(res11); // >>> [ { a: 2, b: 3 } ]
+
+const res12 = await client.json.get('doc', { path: '$[?(@.a + @.b) * 2 == 10]' });
+console.log(res12); // >>> [ { a: 2, b: 3 } ]
+// STEP_END
+
+// REMOVE_START
+assert.deepEqual(res10, [{ a: 2, b: 3 }]);
+assert.deepEqual(res11, [{ a: 2, b: 3 }]);
+assert.deepEqual(res12, [{ a: 2, b: 3 }]);
+await client.del('doc');
+// REMOVE_END
+
+// STEP_START filter_membership
+const res13 = await client.json.set('doc', '$', { a: [1, 2, 3, 4], allow: [2, 3] });
+console.log(res13); // >>> OK
+
+const res14 = await client.json.get('doc', { path: '$.a[?@ in [2,4]]' });
+console.log(res14); // >>> [ 2, 4 ]
+
+const res15 = await client.json.get('doc', { path: '$.a[?@ nin [2,4]]' });
+console.log(res15); // >>> [ 1, 3 ]
+
+const res16 = await client.json.get('doc', { path: '$.a[?@ in $.allow]' });
+console.log(res16); // >>> [ 2, 3 ]
+// STEP_END
+
+// REMOVE_START
+assert.deepEqual(res14, [2, 4]);
+assert.deepEqual(res15, [1, 3]);
+assert.deepEqual(res16, [2, 3]);
+await client.del('doc');
+// REMOVE_END
+
+// STEP_START filter_set_relations
+const res17 = await client.json.set('doc', '$', { a: [[1, 2], [1, 5], []] });
+console.log(res17); // >>> OK
+
+const res18 = await client.json.get('doc', { path: '$.a[?@ subsetof [1,2,3]]' });
+console.log(res18); // >>> [ [ 1, 2 ], [] ]
+
+const res19 = await client.json.set('doc', '$', { a: [[1, 9], [8, 9], []] });
+console.log(res19); // >>> OK
+
+const res20 = await client.json.get('doc', { path: '$.a[?@ anyof [1,2,3]]' });
+console.log(res20); // >>> [ [ 1, 9 ] ]
+
+const res21 = await client.json.set('doc', '$', { a: [[4, 5], [1, 9], []] });
+console.log(res21); // >>> OK
+
+const res22 = await client.json.get('doc', { path: '$.a[?@ noneof [1,2,3]]' });
+console.log(res22); // >>> [ [ 4, 5 ], [] ]
+// STEP_END
+
+// REMOVE_START
+assert.deepEqual(res18, [[1, 2], []]);
+assert.deepEqual(res20, [[1, 9]]);
+assert.deepEqual(res22, [[4, 5], []]);
+await client.del('doc');
+// REMOVE_END
+
+// STEP_START filter_size_empty
+const res23 = await client.json.set('doc', '$', { a: [[4, 5], [1], [7, 8, 9]] });
+console.log(res23); // >>> OK
+
+const res24 = await client.json.get('doc', { path: '$.a[?@ sizeof 2]' });
+console.log(res24); // >>> [ [ 4, 5 ] ]
+
+const res25 = await client.json.set('doc', '$', { a: [[], [1], '', [2, 3], {}, { k: 1 }] });
+console.log(res25); // >>> OK
+
+const res26 = await client.json.get('doc', { path: '$.a[?@ empty true]' });
+console.log(res26); // >>> [ [], '', {} ]
+
+const res27 = await client.json.get('doc', { path: '$.a[?@ empty false]' });
+console.log(res27); // >>> [ [ 1 ], [ 2, 3 ], { k: 1 } ]
+// STEP_END
+
+// REMOVE_START
+assert.deepEqual(res24, [[4, 5]]);
+assert.deepEqual(res26, [[], '', {}]);
+assert.deepEqual(res27, [[1], [2, 3], { k: 1 }]);
+await client.del('doc');
+// REMOVE_END
+
+// STEP_START filter_getkeys
+const res28 = await client.json.set('doc', '$', {
+ obj: { x: 1, y: 2 },
+ books: [{ t: 'a' }, { t: 'b' }]
+});
+console.log(res28); // >>> OK
+
+const res29 = await client.json.get('doc', { path: '$.obj~' });
+console.log(res29); // >>> [ 'x', 'y' ]
+
+const res30 = await client.json.get('doc', { path: '$~' });
+console.log(res30); // >>> [ 'obj', 'books' ]
+
+const res31 = await client.json.get('doc', { path: '$.books~' });
+console.log(res31); // >>> []
+// STEP_END
+
+// REMOVE_START
+assert.deepEqual(res29, ['x', 'y']);
+assert.deepEqual(res30, ['obj', 'books']);
+assert.deepEqual(res31, []);
+await client.del('doc');
+// REMOVE_END
+
+
+// HIDE_START
+await client.close();
+// HIDE_END
diff --git a/local_examples/json_path_ops/nredisstack/JsonPathOpsExample.cs b/local_examples/json_path_ops/nredisstack/JsonPathOpsExample.cs
new file mode 100644
index 0000000000..6e19daeb46
--- /dev/null
+++ b/local_examples/json_path_ops/nredisstack/JsonPathOpsExample.cs
@@ -0,0 +1,222 @@
+// EXAMPLE: json_path_ops
+// HIDE_START
+
+using System.Text.Json;
+using NRedisStack;
+using NRedisStack.RedisStackCommands;
+using NRedisStack.Tests;
+using StackExchange.Redis;
+
+// HIDE_END
+
+// REMOVE_START
+namespace Doc;
+
+[Collection("DocsTests")]
+// REMOVE_END
+
+// HIDE_START
+public class JsonPathOpsExample
+// REMOVE_START
+: AbstractNRedisStackTest, IDisposable
+// REMOVE_END
+{
+ // REMOVE_START
+ public JsonPathOpsExample(EndpointsFixture fixture) : base(fixture) { }
+
+ [Fact]
+ // REMOVE_END
+ public void Run()
+ {
+ //REMOVE_START
+ // This is needed because we're constructing ConfigurationOptions in the test before calling GetConnection
+ SkipIfTargetConnectionDoesNotExist(EndpointsFixture.Env.Standalone);
+ var _ = GetCleanDatabase(EndpointsFixture.Env.Standalone);
+ //REMOVE_END
+ var muxer = ConnectionMultiplexer.Connect("localhost:6379");
+ var db = muxer.GetDatabase();
+ // REMOVE_START
+ // Clear any keys here before using them in tests.
+ db.KeyDelete("doc");
+ // REMOVE_END
+ // HIDE_END
+
+ // STEP_START filter_negation
+ bool res1 = db.JSON().Set("doc", "$", "[{\"a\":1,\"b\":1},{\"b\":2},{\"a\":1},{\"c\":3}]");
+ Console.WriteLine(res1); // >>> True
+
+ RedisResult res2 = db.JSON().Get("doc", path: "$[?!@.a]");
+ Console.WriteLine(res2); // >>> [{"b":2},{"c":3}]
+
+ RedisResult res3 = db.JSON().Get("doc", path: "$[?!(@.a==1)]");
+ Console.WriteLine(res3); // >>> [{"b":2},{"c":3}]
+
+ RedisResult res4 = db.JSON().Get("doc", path: "$[?!@.a && @.b]");
+ Console.WriteLine(res4); // >>> [{"b":2}]
+ // STEP_END
+
+ // REMOVE_START
+ Assert.True(res1);
+ Assert.Equal("[{\"b\":2},{\"c\":3}]", (string?)res2);
+ Assert.Equal("[{\"b\":2},{\"c\":3}]", (string?)res3);
+ Assert.Equal("[{\"b\":2}]", (string?)res4);
+ db.KeyDelete("doc");
+ // REMOVE_END
+
+ // STEP_START filter_literal_eq
+ bool res5 = db.JSON().Set(
+ "doc",
+ "$",
+ "{\"arrs\":[[1],[2],[1,2],[1,[2]]],\"objs\":[{\"x\":1},{\"x\":2},{\"y\":1}]}"
+ );
+ Console.WriteLine(res5); // >>> True
+
+ RedisResult res6 = db.JSON().Get("doc", path: "$.arrs[?(@ == [1])]");
+ Console.WriteLine(res6); // >>> [[1]]
+
+ RedisResult res7 = db.JSON().Get("doc", path: "$.arrs[?(@ == [1,[2]])]");
+ Console.WriteLine(res7); // >>> [[1,[2]]]
+
+ RedisResult res8 = db.JSON().Get("doc", path: "$.objs[?(@ == {\"x\":1})]");
+ Console.WriteLine(res8); // >>> [{"x":1}]
+ // STEP_END
+
+ // REMOVE_START
+ Assert.True(res5);
+ Assert.Equal("[[1]]", (string?)res6);
+ Assert.Equal("[[1,[2]]]", (string?)res7);
+ Assert.Equal("[{\"x\":1}]", (string?)res8);
+ db.KeyDelete("doc");
+ // REMOVE_END
+
+ // STEP_START filter_arithmetic
+ bool res9 = db.JSON().Set("doc", "$", "[{\"a\":2,\"b\":3},{\"a\":5,\"b\":2}]");
+ Console.WriteLine(res9); // >>> True
+
+ RedisResult res10 = db.JSON().Get("doc", path: "$[?@.a + 1 == 3]");
+ Console.WriteLine(res10); // >>> [{"a":2,"b":3}]
+
+ RedisResult res11 = db.JSON().Get("doc", path: "$[?@.a + @.b * 2 == 8]");
+ Console.WriteLine(res11); // >>> [{"a":2,"b":3}]
+
+ RedisResult res12 = db.JSON().Get("doc", path: "$[?(@.a + @.b) * 2 == 10]");
+ Console.WriteLine(res12); // >>> [{"a":2,"b":3}]
+ // STEP_END
+
+ // REMOVE_START
+ Assert.True(res9);
+ Assert.Equal("[{\"a\":2,\"b\":3}]", (string?)res10);
+ Assert.Equal("[{\"a\":2,\"b\":3}]", (string?)res11);
+ Assert.Equal("[{\"a\":2,\"b\":3}]", (string?)res12);
+ db.KeyDelete("doc");
+ // REMOVE_END
+
+ // STEP_START filter_membership
+ bool res13 = db.JSON().Set("doc", "$", "{\"a\":[1,2,3,4],\"allow\":[2,3]}");
+ Console.WriteLine(res13); // >>> True
+
+ RedisResult res14 = db.JSON().Get("doc", path: "$.a[?@ in [2,4]]");
+ Console.WriteLine(res14); // >>> [2,4]
+
+ RedisResult res15 = db.JSON().Get("doc", path: "$.a[?@ nin [2,4]]");
+ Console.WriteLine(res15); // >>> [1,3]
+
+ RedisResult res16 = db.JSON().Get("doc", path: "$.a[?@ in $.allow]");
+ Console.WriteLine(res16); // >>> [2,3]
+ // STEP_END
+
+ // REMOVE_START
+ Assert.True(res13);
+ Assert.Equal("[2,4]", (string?)res14);
+ Assert.Equal("[1,3]", (string?)res15);
+ Assert.Equal("[2,3]", (string?)res16);
+ db.KeyDelete("doc");
+ // REMOVE_END
+
+ // STEP_START filter_set_relations
+ bool res17 = db.JSON().Set("doc", "$", "{\"a\":[[1,2],[1,5],[]]}");
+ Console.WriteLine(res17); // >>> True
+
+ RedisResult res18 = db.JSON().Get("doc", path: "$.a[?@ subsetof [1,2,3]]");
+ Console.WriteLine(res18); // >>> [[1,2],[]]
+
+ bool res19 = db.JSON().Set("doc", "$", "{\"a\":[[1,9],[8,9],[]]}");
+ Console.WriteLine(res19); // >>> True
+
+ RedisResult res20 = db.JSON().Get("doc", path: "$.a[?@ anyof [1,2,3]]");
+ Console.WriteLine(res20); // >>> [[1,9]]
+
+ bool res21 = db.JSON().Set("doc", "$", "{\"a\":[[4,5],[1,9],[]]}");
+ Console.WriteLine(res21); // >>> True
+
+ RedisResult res22 = db.JSON().Get("doc", path: "$.a[?@ noneof [1,2,3]]");
+ Console.WriteLine(res22); // >>> [[4,5],[]]
+ // STEP_END
+
+ // REMOVE_START
+ Assert.True(res17);
+ Assert.Equal("[[1,2],[]]", (string?)res18);
+ Assert.True(res19);
+ Assert.Equal("[[1,9]]", (string?)res20);
+ Assert.True(res21);
+ Assert.Equal("[[4,5],[]]", (string?)res22);
+ db.KeyDelete("doc");
+ // REMOVE_END
+
+ // STEP_START filter_size_empty
+ bool res23 = db.JSON().Set("doc", "$", "{\"a\":[[4,5],[1],[7,8,9]]}");
+ Console.WriteLine(res23); // >>> True
+
+ RedisResult res24 = db.JSON().Get("doc", path: "$.a[?@ sizeof 2]");
+ Console.WriteLine(res24); // >>> [[4,5]]
+
+ bool res25 = db.JSON().Set("doc", "$", "{\"a\":[[],[1],\"\",[2,3],{},{\"k\":1}]}");
+ Console.WriteLine(res25); // >>> True
+
+ RedisResult res26 = db.JSON().Get("doc", path: "$.a[?@ empty true]");
+ Console.WriteLine(res26); // >>> [[],"",{}]
+
+ RedisResult res27 = db.JSON().Get("doc", path: "$.a[?@ empty false]");
+ Console.WriteLine(res27); // >>> [[1],[2,3],{"k":1}]
+ // STEP_END
+
+ // REMOVE_START
+ Assert.True(res23);
+ Assert.Equal("[[4,5]]", (string?)res24);
+ Assert.True(res25);
+ Assert.Equal("[[],\"\",{}]", (string?)res26);
+ Assert.Equal("[[1],[2,3],{\"k\":1}]", (string?)res27);
+ db.KeyDelete("doc");
+ // REMOVE_END
+
+ // STEP_START filter_getkeys
+ bool res28 = db.JSON().Set(
+ "doc",
+ "$",
+ "{\"obj\":{\"x\":1,\"y\":2},\"books\":[{\"t\":\"a\"},{\"t\":\"b\"}]}"
+ );
+ Console.WriteLine(res28); // >>> True
+
+ RedisResult res29 = db.JSON().Get("doc", path: "$.obj~");
+ Console.WriteLine(res29); // >>> ["x","y"]
+
+ RedisResult res30 = db.JSON().Get("doc", path: "$~");
+ Console.WriteLine(res30); // >>> ["obj","books"]
+
+ RedisResult res31 = db.JSON().Get("doc", path: "$.books~");
+ Console.WriteLine(res31); // >>> []
+ // STEP_END
+
+ // REMOVE_START
+ Assert.True(res28);
+ Assert.Equal("[\"x\",\"y\"]", (string?)res29);
+ Assert.Equal("[\"obj\",\"books\"]", (string?)res30);
+ Assert.Equal("[]", (string?)res31);
+ db.KeyDelete("doc");
+ // REMOVE_END
+
+
+ // HIDE_START
+ }
+}
+// HIDE_END
diff --git a/local_examples/json_path_ops/predis/JsonPathOpsTest.php b/local_examples/json_path_ops/predis/JsonPathOpsTest.php
new file mode 100644
index 0000000000..1afecbc588
--- /dev/null
+++ b/local_examples/json_path_ops/predis/JsonPathOpsTest.php
@@ -0,0 +1,223 @@
+// EXAMPLE: json_path_ops
+redis = new PredisClient([
+ 'scheme' => 'tcp',
+ 'host' => '127.0.0.1',
+ 'port' => 6379,
+ 'password' => '',
+ 'database' => 0,
+ ]);
+
+ // Clean up before each test
+ $this->redis->flushall();
+ }
+
+ public function testJsonPathOps(): void
+ {
+ // STEP_START filter_negation
+ $res1 = $this->redis->jsonset('doc', '$', json_encode(
+ [["a" => 1, "b" => 1], ["b" => 2], ["a" => 1], ["c" => 3]]
+ ));
+ echo $res1 . PHP_EOL; // >>> OK
+
+ $res2 = $this->redis->jsonget('doc', '', '', '', '$[?!@.a]');
+ echo $res2 . PHP_EOL; // >>> [{"b":2},{"c":3}]
+
+ $res3 = $this->redis->jsonget('doc', '', '', '', '$[?!(@.a==1)]');
+ echo $res3 . PHP_EOL; // >>> [{"b":2},{"c":3}]
+
+ $res4 = $this->redis->jsonget('doc', '', '', '', '$[?!@.a && @.b]');
+ echo $res4 . PHP_EOL; // >>> [{"b":2}]
+ // STEP_END
+
+ // REMOVE_START
+ $this->assertEquals('OK', $res1);
+ $this->assertEquals('[{"b":2},{"c":3}]', $res2);
+ $this->assertEquals('[{"b":2},{"c":3}]', $res3);
+ $this->assertEquals('[{"b":2}]', $res4);
+ $this->redis->del('doc');
+ // REMOVE_END
+
+ // STEP_START filter_literal_eq
+ $res5 = $this->redis->jsonset('doc', '$', json_encode([
+ "arrs" => [[1], [2], [1, 2], [1, [2]]],
+ "objs" => [["x" => 1], ["x" => 2], ["y" => 1]],
+ ]));
+ echo $res5 . PHP_EOL; // >>> OK
+
+ $res6 = $this->redis->jsonget('doc', '', '', '', '$.arrs[?(@ == [1])]');
+ echo $res6 . PHP_EOL; // >>> [[1]]
+
+ $res7 = $this->redis->jsonget('doc', '', '', '', '$.arrs[?(@ == [1,[2]])]');
+ echo $res7 . PHP_EOL; // >>> [[1,[2]]]
+
+ $res8 = $this->redis->jsonget('doc', '', '', '', '$.objs[?(@ == {"x":1})]');
+ echo $res8 . PHP_EOL; // >>> [{"x":1}]
+ // STEP_END
+
+ // REMOVE_START
+ $this->assertEquals('OK', $res5);
+ $this->assertEquals('[[1]]', $res6);
+ $this->assertEquals('[[1,[2]]]', $res7);
+ $this->assertEquals('[{"x":1}]', $res8);
+ $this->redis->del('doc');
+ // REMOVE_END
+
+ // STEP_START filter_arithmetic
+ $res9 = $this->redis->jsonset('doc', '$', json_encode(
+ [["a" => 2, "b" => 3], ["a" => 5, "b" => 2]]
+ ));
+ echo $res9 . PHP_EOL; // >>> OK
+
+ $res10 = $this->redis->jsonget('doc', '', '', '', '$[?@.a + 1 == 3]');
+ echo $res10 . PHP_EOL; // >>> [{"a":2,"b":3}]
+
+ $res11 = $this->redis->jsonget('doc', '', '', '', '$[?@.a + @.b * 2 == 8]');
+ echo $res11 . PHP_EOL; // >>> [{"a":2,"b":3}]
+
+ $res12 = $this->redis->jsonget('doc', '', '', '', '$[?(@.a + @.b) * 2 == 10]');
+ echo $res12 . PHP_EOL; // >>> [{"a":2,"b":3}]
+ // STEP_END
+
+ // REMOVE_START
+ $this->assertEquals('OK', $res9);
+ $this->assertEquals('[{"a":2,"b":3}]', $res10);
+ $this->assertEquals('[{"a":2,"b":3}]', $res11);
+ $this->assertEquals('[{"a":2,"b":3}]', $res12);
+ $this->redis->del('doc');
+ // REMOVE_END
+
+ // STEP_START filter_membership
+ $res13 = $this->redis->jsonset('doc', '$', json_encode(
+ ["a" => [1, 2, 3, 4], "allow" => [2, 3]]
+ ));
+ echo $res13 . PHP_EOL; // >>> OK
+
+ $res14 = $this->redis->jsonget('doc', '', '', '', '$.a[?@ in [2,4]]');
+ echo $res14 . PHP_EOL; // >>> [2,4]
+
+ $res15 = $this->redis->jsonget('doc', '', '', '', '$.a[?@ nin [2,4]]');
+ echo $res15 . PHP_EOL; // >>> [1,3]
+
+ $res16 = $this->redis->jsonget('doc', '', '', '', '$.a[?@ in $.allow]');
+ echo $res16 . PHP_EOL; // >>> [2,3]
+ // STEP_END
+
+ // REMOVE_START
+ $this->assertEquals('OK', $res13);
+ $this->assertEquals('[2,4]', $res14);
+ $this->assertEquals('[1,3]', $res15);
+ $this->assertEquals('[2,3]', $res16);
+ $this->redis->del('doc');
+ // REMOVE_END
+
+ // STEP_START filter_set_relations
+ $res17 = $this->redis->jsonset('doc', '$', json_encode(
+ ["a" => [[1, 2], [1, 5], []]]
+ ));
+ echo $res17 . PHP_EOL; // >>> OK
+
+ $res18 = $this->redis->jsonget('doc', '', '', '', '$.a[?@ subsetof [1,2,3]]');
+ echo $res18 . PHP_EOL; // >>> [[1,2],[]]
+
+ $res19 = $this->redis->jsonset('doc', '$', json_encode(
+ ["a" => [[1, 9], [8, 9], []]]
+ ));
+ echo $res19 . PHP_EOL; // >>> OK
+
+ $res20 = $this->redis->jsonget('doc', '', '', '', '$.a[?@ anyof [1,2,3]]');
+ echo $res20 . PHP_EOL; // >>> [[1,9]]
+
+ $res21 = $this->redis->jsonset('doc', '$', json_encode(
+ ["a" => [[4, 5], [1, 9], []]]
+ ));
+ echo $res21 . PHP_EOL; // >>> OK
+
+ $res22 = $this->redis->jsonget('doc', '', '', '', '$.a[?@ noneof [1,2,3]]');
+ echo $res22 . PHP_EOL; // >>> [[4,5],[]]
+ // STEP_END
+
+ // REMOVE_START
+ $this->assertEquals('OK', $res17);
+ $this->assertEquals('[[1,2],[]]', $res18);
+ $this->assertEquals('OK', $res19);
+ $this->assertEquals('[[1,9]]', $res20);
+ $this->assertEquals('OK', $res21);
+ $this->assertEquals('[[4,5],[]]', $res22);
+ $this->redis->del('doc');
+ // REMOVE_END
+
+ // STEP_START filter_size_empty
+ $res23 = $this->redis->jsonset('doc', '$', json_encode(
+ ["a" => [[4, 5], [1], [7, 8, 9]]]
+ ));
+ echo $res23 . PHP_EOL; // >>> OK
+
+ $res24 = $this->redis->jsonget('doc', '', '', '', '$.a[?@ sizeof 2]');
+ echo $res24 . PHP_EOL; // >>> [[4,5]]
+
+ $res25 = $this->redis->jsonset('doc', '$', json_encode(
+ ["a" => [[], [1], "", [2, 3], new \stdClass(), ["k" => 1]]]
+ ));
+ echo $res25 . PHP_EOL; // >>> OK
+
+ $res26 = $this->redis->jsonget('doc', '', '', '', '$.a[?@ empty true]');
+ echo $res26 . PHP_EOL; // >>> [[],"",{}]
+
+ $res27 = $this->redis->jsonget('doc', '', '', '', '$.a[?@ empty false]');
+ echo $res27 . PHP_EOL; // >>> [[1],[2,3],{"k":1}]
+ // STEP_END
+
+ // REMOVE_START
+ $this->assertEquals('OK', $res23);
+ $this->assertEquals('[[4,5]]', $res24);
+ $this->assertEquals('OK', $res25);
+ $this->assertEquals('[[],"",{}]', $res26);
+ $this->assertEquals('[[1],[2,3],{"k":1}]', $res27);
+ $this->redis->del('doc');
+ // REMOVE_END
+
+ // STEP_START filter_getkeys
+ $res28 = $this->redis->jsonset('doc', '$', json_encode([
+ "obj" => ["x" => 1, "y" => 2],
+ "books" => [["t" => "a"], ["t" => "b"]],
+ ]));
+ echo $res28 . PHP_EOL; // >>> OK
+
+ $res29 = $this->redis->jsonget('doc', '', '', '', '$.obj~');
+ echo $res29 . PHP_EOL; // >>> ["x","y"]
+
+ $res30 = $this->redis->jsonget('doc', '', '', '', '$~');
+ echo $res30 . PHP_EOL; // >>> ["obj","books"]
+
+ $res31 = $this->redis->jsonget('doc', '', '', '', '$.books~');
+ echo $res31 . PHP_EOL; // >>> []
+ // STEP_END
+
+ // REMOVE_START
+ $this->assertEquals('OK', $res28);
+ $this->assertEquals('["x","y"]', $res29);
+ $this->assertEquals('["obj","books"]', $res30);
+ $this->assertEquals('[]', $res31);
+ $this->redis->del('doc');
+ // REMOVE_END
+
+ }
+
+ protected function tearDown(): void
+ {
+ // Clean up after each test
+ $this->redis->flushall();
+ $this->redis->disconnect();
+ }
+}
diff --git a/local_examples/json_path_ops/redis-py/json_path_ops.py b/local_examples/json_path_ops/redis-py/json_path_ops.py
new file mode 100644
index 0000000000..430ed2b265
--- /dev/null
+++ b/local_examples/json_path_ops/redis-py/json_path_ops.py
@@ -0,0 +1,201 @@
+# EXAMPLE: json_path_ops
+# HIDE_START
+import redis
+
+r = redis.Redis(decode_responses=True)
+# HIDE_END
+
+# REMOVE_START
+r.delete("doc")
+# REMOVE_END
+
+# STEP_START filter_negation
+res1 = r.json().set("doc", "$", [{"a": 1, "b": 1}, {"b": 2}, {"a": 1}, {"c": 3}])
+print(res1)
+# >>> True
+
+res2 = r.json().get("doc", "$[?!@.a]")
+print(res2)
+# >>> [{'b': 2}, {'c': 3}]
+
+res3 = r.json().get("doc", "$[?!(@.a==1)]")
+print(res3)
+# >>> [{'b': 2}, {'c': 3}]
+
+res4 = r.json().get("doc", "$[?!@.a && @.b]")
+print(res4)
+# >>> [{'b': 2}]
+# STEP_END
+
+# REMOVE_START
+assert res2 == [{"b": 2}, {"c": 3}]
+assert res3 == [{"b": 2}, {"c": 3}]
+assert res4 == [{"b": 2}]
+r.delete("doc")
+# REMOVE_END
+
+# STEP_START filter_literal_eq
+res1 = r.json().set(
+ "doc",
+ "$",
+ {"arrs": [[1], [2], [1, 2], [1, [2]]], "objs": [{"x": 1}, {"x": 2}, {"y": 1}]},
+)
+print(res1)
+# >>> True
+
+res2 = r.json().get("doc", "$.arrs[?(@ == [1])]")
+print(res2)
+# >>> [[1]]
+
+res3 = r.json().get("doc", "$.arrs[?(@ == [1,[2]])]")
+print(res3)
+# >>> [[1, [2]]]
+
+res4 = r.json().get("doc", '$.objs[?(@ == {"x":1})]')
+print(res4)
+# >>> [{'x': 1}]
+# STEP_END
+
+# REMOVE_START
+assert res2 == [[1]]
+assert res3 == [[1, [2]]]
+assert res4 == [{"x": 1}]
+r.delete("doc")
+# REMOVE_END
+
+# STEP_START filter_arithmetic
+res1 = r.json().set("doc", "$", [{"a": 2, "b": 3}, {"a": 5, "b": 2}])
+print(res1)
+# >>> True
+
+res2 = r.json().get("doc", "$[?@.a + 1 == 3]")
+print(res2)
+# >>> [{'a': 2, 'b': 3}]
+
+res3 = r.json().get("doc", "$[?@.a + @.b * 2 == 8]")
+print(res3)
+# >>> [{'a': 2, 'b': 3}]
+
+res4 = r.json().get("doc", "$[?(@.a + @.b) * 2 == 10]")
+print(res4)
+# >>> [{'a': 2, 'b': 3}]
+# STEP_END
+
+# REMOVE_START
+assert res2 == [{"a": 2, "b": 3}]
+assert res3 == [{"a": 2, "b": 3}]
+assert res4 == [{"a": 2, "b": 3}]
+r.delete("doc")
+# REMOVE_END
+
+# STEP_START filter_membership
+res1 = r.json().set("doc", "$", {"a": [1, 2, 3, 4], "allow": [2, 3]})
+print(res1)
+# >>> True
+
+res2 = r.json().get("doc", "$.a[?@ in [2,4]]")
+print(res2)
+# >>> [2, 4]
+
+res3 = r.json().get("doc", "$.a[?@ nin [2,4]]")
+print(res3)
+# >>> [1, 3]
+
+res4 = r.json().get("doc", "$.a[?@ in $.allow]")
+print(res4)
+# >>> [2, 3]
+# STEP_END
+
+# REMOVE_START
+assert res2 == [2, 4]
+assert res3 == [1, 3]
+assert res4 == [2, 3]
+r.delete("doc")
+# REMOVE_END
+
+# STEP_START filter_set_relations
+res1 = r.json().set("doc", "$", {"a": [[1, 2], [1, 5], []]})
+print(res1)
+# >>> True
+
+res2 = r.json().get("doc", "$.a[?@ subsetof [1,2,3]]")
+print(res2)
+# >>> [[1, 2], []]
+
+res3 = r.json().set("doc", "$", {"a": [[1, 9], [8, 9], []]})
+print(res3)
+# >>> True
+
+res4 = r.json().get("doc", "$.a[?@ anyof [1,2,3]]")
+print(res4)
+# >>> [[1, 9]]
+
+res5 = r.json().set("doc", "$", {"a": [[4, 5], [1, 9], []]})
+print(res5)
+# >>> True
+
+res6 = r.json().get("doc", "$.a[?@ noneof [1,2,3]]")
+print(res6)
+# >>> [[4, 5], []]
+# STEP_END
+
+# REMOVE_START
+assert res2 == [[1, 2], []]
+assert res4 == [[1, 9]]
+assert res6 == [[4, 5], []]
+r.delete("doc")
+# REMOVE_END
+
+# STEP_START filter_size_empty
+res1 = r.json().set("doc", "$", {"a": [[4, 5], [1], [7, 8, 9]]})
+print(res1)
+# >>> True
+
+res2 = r.json().get("doc", "$.a[?@ sizeof 2]")
+print(res2)
+# >>> [[4, 5]]
+
+res3 = r.json().set("doc", "$", {"a": [[], [1], "", [2, 3], {}, {"k": 1}]})
+print(res3)
+# >>> True
+
+res4 = r.json().get("doc", "$.a[?@ empty true]")
+print(res4)
+# >>> [[], '', {}]
+
+res5 = r.json().get("doc", "$.a[?@ empty false]")
+print(res5)
+# >>> [[1], [2, 3], {'k': 1}]
+# STEP_END
+
+# REMOVE_START
+assert res2 == [[4, 5]]
+assert res4 == [[], "", {}]
+assert res5 == [[1], [2, 3], {"k": 1}]
+r.delete("doc")
+# REMOVE_END
+
+# STEP_START filter_getkeys
+res1 = r.json().set("doc", "$", {"obj": {"x": 1, "y": 2}, "books": [{"t": "a"}, {"t": "b"}]})
+print(res1)
+# >>> True
+
+res2 = r.json().get("doc", "$.obj~")
+print(res2)
+# >>> ['x', 'y']
+
+res3 = r.json().get("doc", "$~")
+print(res3)
+# >>> ['obj', 'books']
+
+res4 = r.json().get("doc", "$.books~")
+print(res4)
+# >>> []
+# STEP_END
+
+# REMOVE_START
+assert res2 == ["x", "y"]
+assert res3 == ["obj", "books"]
+assert res4 == []
+r.delete("doc")
+# REMOVE_END
diff --git a/local_examples/json_path_ops/ruby/json_path_ops.rb b/local_examples/json_path_ops/ruby/json_path_ops.rb
new file mode 100644
index 0000000000..717377fe3d
--- /dev/null
+++ b/local_examples/json_path_ops/ruby/json_path_ops.rb
@@ -0,0 +1,174 @@
+# EXAMPLE: json_path_ops
+# 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('doc')
+# REMOVE_END
+
+# STEP_START filter_negation
+res1 = r.json_set('doc', '$', [{ 'a' => 1, 'b' => 1 }, { 'b' => 2 }, { 'a' => 1 }, { 'c' => 3 }])
+puts res1 # >>> OK
+
+res2 = r.json_get('doc', '$[?!@.a]')
+p res2 # >>> [{"b"=>2}, {"c"=>3}]
+
+res3 = r.json_get('doc', '$[?!(@.a==1)]')
+p res3 # >>> [{"b"=>2}, {"c"=>3}]
+
+res4 = r.json_get('doc', '$[?!@.a && @.b]')
+p res4 # >>> [{"b"=>2}]
+# STEP_END
+
+# REMOVE_START
+assert_equal([{ 'b' => 2 }, { 'c' => 3 }], res2)
+assert_equal([{ 'b' => 2 }, { 'c' => 3 }], res3)
+assert_equal([{ 'b' => 2 }], res4)
+r.del('doc')
+# REMOVE_END
+
+# STEP_START filter_literal_eq
+res1 = r.json_set(
+ 'doc', '$',
+ { 'arrs' => [[1], [2], [1, 2], [1, [2]]], 'objs' => [{ 'x' => 1 }, { 'x' => 2 }, { 'y' => 1 }] }
+)
+puts res1 # >>> OK
+
+res2 = r.json_get('doc', '$.arrs[?(@ == [1])]')
+p res2 # >>> [[1]]
+
+res3 = r.json_get('doc', '$.arrs[?(@ == [1,[2]])]')
+p res3 # >>> [[1, [2]]]
+
+res4 = r.json_get('doc', '$.objs[?(@ == {"x":1})]')
+p res4 # >>> [{"x"=>1}]
+# STEP_END
+
+# REMOVE_START
+assert_equal([[1]], res2)
+assert_equal([[1, [2]]], res3)
+assert_equal([{ 'x' => 1 }], res4)
+r.del('doc')
+# REMOVE_END
+
+# STEP_START filter_arithmetic
+res1 = r.json_set('doc', '$', [{ 'a' => 2, 'b' => 3 }, { 'a' => 5, 'b' => 2 }])
+puts res1 # >>> OK
+
+res2 = r.json_get('doc', '$[?@.a + 1 == 3]')
+p res2 # >>> [{"a"=>2, "b"=>3}]
+
+res3 = r.json_get('doc', '$[?@.a + @.b * 2 == 8]')
+p res3 # >>> [{"a"=>2, "b"=>3}]
+
+res4 = r.json_get('doc', '$[?(@.a + @.b) * 2 == 10]')
+p res4 # >>> [{"a"=>2, "b"=>3}]
+# STEP_END
+
+# REMOVE_START
+assert_equal([{ 'a' => 2, 'b' => 3 }], res2)
+assert_equal([{ 'a' => 2, 'b' => 3 }], res3)
+assert_equal([{ 'a' => 2, 'b' => 3 }], res4)
+r.del('doc')
+# REMOVE_END
+
+# STEP_START filter_membership
+res1 = r.json_set('doc', '$', { 'a' => [1, 2, 3, 4], 'allow' => [2, 3] })
+puts res1 # >>> OK
+
+res2 = r.json_get('doc', '$.a[?@ in [2,4]]')
+p res2 # >>> [2, 4]
+
+res3 = r.json_get('doc', '$.a[?@ nin [2,4]]')
+p res3 # >>> [1, 3]
+
+res4 = r.json_get('doc', '$.a[?@ in $.allow]')
+p res4 # >>> [2, 3]
+# STEP_END
+
+# REMOVE_START
+assert_equal([2, 4], res2)
+assert_equal([1, 3], res3)
+assert_equal([2, 3], res4)
+r.del('doc')
+# REMOVE_END
+
+# STEP_START filter_set_relations
+res1 = r.json_set('doc', '$', { 'a' => [[1, 2], [1, 5], []] })
+puts res1 # >>> OK
+
+res2 = r.json_get('doc', '$.a[?@ subsetof [1,2,3]]')
+p res2 # >>> [[1, 2], []]
+
+res3 = r.json_set('doc', '$', { 'a' => [[1, 9], [8, 9], []] })
+puts res3 # >>> OK
+
+res4 = r.json_get('doc', '$.a[?@ anyof [1,2,3]]')
+p res4 # >>> [[1, 9]]
+
+res5 = r.json_set('doc', '$', { 'a' => [[4, 5], [1, 9], []] })
+puts res5 # >>> OK
+
+res6 = r.json_get('doc', '$.a[?@ noneof [1,2,3]]')
+p res6 # >>> [[4, 5], []]
+# STEP_END
+
+# REMOVE_START
+assert_equal([[1, 2], []], res2)
+assert_equal([[1, 9]], res4)
+assert_equal([[4, 5], []], res6)
+r.del('doc')
+# REMOVE_END
+
+# STEP_START filter_size_empty
+res1 = r.json_set('doc', '$', { 'a' => [[4, 5], [1], [7, 8, 9]] })
+puts res1 # >>> OK
+
+res2 = r.json_get('doc', '$.a[?@ sizeof 2]')
+p res2 # >>> [[4, 5]]
+
+res3 = r.json_set('doc', '$', { 'a' => [[], [1], '', [2, 3], {}, { 'k' => 1 }] })
+puts res3 # >>> OK
+
+res4 = r.json_get('doc', '$.a[?@ empty true]')
+p res4 # >>> [[], "", {}]
+
+res5 = r.json_get('doc', '$.a[?@ empty false]')
+p res5 # >>> [[1], [2, 3], {"k"=>1}]
+# STEP_END
+
+# REMOVE_START
+assert_equal([[4, 5]], res2)
+assert_equal([[], '', {}], res4)
+assert_equal([[1], [2, 3], { 'k' => 1 }], res5)
+r.del('doc')
+# REMOVE_END
+
+# STEP_START filter_getkeys
+res1 = r.json_set('doc', '$', { 'obj' => { 'x' => 1, 'y' => 2 }, 'books' => [{ 't' => 'a' }, { 't' => 'b' }] })
+puts res1 # >>> OK
+
+res2 = r.json_get('doc', '$.obj~')
+p res2 # >>> ["x", "y"]
+
+res3 = r.json_get('doc', '$~')
+p res3 # >>> ["obj", "books"]
+
+res4 = r.json_get('doc', '$.books~')
+p res4 # >>> []
+# STEP_END
+
+# REMOVE_START
+assert_equal(%w[x y], res2)
+assert_equal(%w[obj books], res3)
+assert_equal([], res4)
+r.del('doc')
+r.close
+# REMOVE_END
diff --git a/local_examples/json_path_ops/rust-async/json_path_ops.rs b/local_examples/json_path_ops/rust-async/json_path_ops.rs
new file mode 100644
index 0000000000..92a637ab23
--- /dev/null
+++ b/local_examples/json_path_ops/rust-async/json_path_ops.rs
@@ -0,0 +1,453 @@
+// EXAMPLE: json_path_ops
+#[cfg(test)]
+mod json_path_ops_tests {
+ use redis::{AsyncCommands, JsonAsyncCommands};
+ use serde_json::json;
+
+ #[tokio::test]
+ async fn run() {
+ let mut r = match redis::Client::open("redis://127.0.0.1") {
+ Ok(client) => {
+ match client.get_multiplexed_async_connection().await {
+ Ok(conn) => conn,
+ Err(e) => {
+ println!("Failed to connect to Redis: {e}");
+ return;
+ }
+ }
+ },
+ Err(e) => {
+ println!("Failed to create Redis client: {e}");
+ return;
+ }
+ };
+
+ // Clean up any existing data
+ let _: Result = r.del("doc").await;
+
+ // STEP_START filter_negation
+ let _: bool = match r.json_set("doc", "$", &json!([{"a":1,"b":1},{"b":2},{"a":1},{"c":3}])).await {
+ Ok(v) => v,
+ Err(e) => {
+ println!("Error setting doc: {e}");
+ return;
+ }
+ };
+
+ match r.json_get("doc", "$[?!@.a]").await {
+ Ok(res1) => {
+ let res1: String = res1;
+ println!("{res1}"); // >>> [{"b":2},{"c":3}]
+ // REMOVE_START
+ assert_eq!(res1, r#"[{"b":2},{"c":3}]"#);
+ // REMOVE_END
+ },
+ Err(e) => {
+ println!("Error getting doc: {e}");
+ return;
+ }
+ }
+
+ match r.json_get("doc", "$[?!(@.a==1)]").await {
+ Ok(res2) => {
+ let res2: String = res2;
+ println!("{res2}"); // >>> [{"b":2},{"c":3}]
+ // REMOVE_START
+ assert_eq!(res2, r#"[{"b":2},{"c":3}]"#);
+ // REMOVE_END
+ },
+ Err(e) => {
+ println!("Error getting doc: {e}");
+ return;
+ }
+ }
+
+ match r.json_get("doc", "$[?!@.a && @.b]").await {
+ Ok(res3) => {
+ let res3: String = res3;
+ println!("{res3}"); // >>> [{"b":2}]
+ // REMOVE_START
+ assert_eq!(res3, r#"[{"b":2}]"#);
+ // REMOVE_END
+ },
+ Err(e) => {
+ println!("Error getting doc: {e}");
+ return;
+ }
+ }
+
+ // REMOVE_START
+ let _: Result = r.del("doc").await;
+ // REMOVE_END
+ // STEP_END
+
+ // STEP_START filter_literal_eq
+ let _: bool = match r.json_set(
+ "doc",
+ "$",
+ &json!({"arrs":[[1],[2],[1,2],[1,[2]]],"objs":[{"x":1},{"x":2},{"y":1}]}),
+ ).await {
+ Ok(v) => v,
+ Err(e) => {
+ println!("Error setting doc: {e}");
+ return;
+ }
+ };
+
+ match r.json_get("doc", "$.arrs[?(@ == [1])]").await {
+ Ok(res4) => {
+ let res4: String = res4;
+ println!("{res4}"); // >>> [[1]]
+ // REMOVE_START
+ assert_eq!(res4, "[[1]]");
+ // REMOVE_END
+ },
+ Err(e) => {
+ println!("Error getting doc: {e}");
+ return;
+ }
+ }
+
+ match r.json_get("doc", "$.arrs[?(@ == [1,[2]])]").await {
+ Ok(res5) => {
+ let res5: String = res5;
+ println!("{res5}"); // >>> [[1,[2]]]
+ // REMOVE_START
+ assert_eq!(res5, "[[1,[2]]]");
+ // REMOVE_END
+ },
+ Err(e) => {
+ println!("Error getting doc: {e}");
+ return;
+ }
+ }
+
+ match r.json_get("doc", r#"$.objs[?(@ == {"x":1})]"#).await {
+ Ok(res6) => {
+ let res6: String = res6;
+ println!("{res6}"); // >>> [{"x":1}]
+ // REMOVE_START
+ assert_eq!(res6, r#"[{"x":1}]"#);
+ // REMOVE_END
+ },
+ Err(e) => {
+ println!("Error getting doc: {e}");
+ return;
+ }
+ }
+
+ // REMOVE_START
+ let _: Result = r.del("doc").await;
+ // REMOVE_END
+ // STEP_END
+
+ // STEP_START filter_arithmetic
+ let _: bool = match r.json_set("doc", "$", &json!([{"a":2,"b":3},{"a":5,"b":2}])).await {
+ Ok(v) => v,
+ Err(e) => {
+ println!("Error setting doc: {e}");
+ return;
+ }
+ };
+
+ match r.json_get("doc", "$[?@.a + 1 == 3]").await {
+ Ok(res7) => {
+ let res7: String = res7;
+ println!("{res7}"); // >>> [{"a":2,"b":3}]
+ // REMOVE_START
+ assert_eq!(res7, r#"[{"a":2,"b":3}]"#);
+ // REMOVE_END
+ },
+ Err(e) => {
+ println!("Error getting doc: {e}");
+ return;
+ }
+ }
+
+ match r.json_get("doc", "$[?@.a + @.b * 2 == 8]").await {
+ Ok(res8) => {
+ let res8: String = res8;
+ println!("{res8}"); // >>> [{"a":2,"b":3}]
+ // REMOVE_START
+ assert_eq!(res8, r#"[{"a":2,"b":3}]"#);
+ // REMOVE_END
+ },
+ Err(e) => {
+ println!("Error getting doc: {e}");
+ return;
+ }
+ }
+
+ match r.json_get("doc", "$[?(@.a + @.b) * 2 == 10]").await {
+ Ok(res9) => {
+ let res9: String = res9;
+ println!("{res9}"); // >>> [{"a":2,"b":3}]
+ // REMOVE_START
+ assert_eq!(res9, r#"[{"a":2,"b":3}]"#);
+ // REMOVE_END
+ },
+ Err(e) => {
+ println!("Error getting doc: {e}");
+ return;
+ }
+ }
+
+ // REMOVE_START
+ let _: Result = r.del("doc").await;
+ // REMOVE_END
+ // STEP_END
+
+ // STEP_START filter_membership
+ let _: bool = match r.json_set("doc", "$", &json!({"a":[1,2,3,4],"allow":[2,3]})).await {
+ Ok(v) => v,
+ Err(e) => {
+ println!("Error setting doc: {e}");
+ return;
+ }
+ };
+
+ match r.json_get("doc", "$.a[?@ in [2,4]]").await {
+ Ok(res10) => {
+ let res10: String = res10;
+ println!("{res10}"); // >>> [2,4]
+ // REMOVE_START
+ assert_eq!(res10, "[2,4]");
+ // REMOVE_END
+ },
+ Err(e) => {
+ println!("Error getting doc: {e}");
+ return;
+ }
+ }
+
+ match r.json_get("doc", "$.a[?@ nin [2,4]]").await {
+ Ok(res11) => {
+ let res11: String = res11;
+ println!("{res11}"); // >>> [1,3]
+ // REMOVE_START
+ assert_eq!(res11, "[1,3]");
+ // REMOVE_END
+ },
+ Err(e) => {
+ println!("Error getting doc: {e}");
+ return;
+ }
+ }
+
+ match r.json_get("doc", "$.a[?@ in $.allow]").await {
+ Ok(res12) => {
+ let res12: String = res12;
+ println!("{res12}"); // >>> [2,3]
+ // REMOVE_START
+ assert_eq!(res12, "[2,3]");
+ // REMOVE_END
+ },
+ Err(e) => {
+ println!("Error getting doc: {e}");
+ return;
+ }
+ }
+
+ // REMOVE_START
+ let _: Result = r.del("doc").await;
+ // REMOVE_END
+ // STEP_END
+
+ // STEP_START filter_set_relations
+ let _: bool = match r.json_set("doc", "$", &json!({"a":[[1,2],[1,5],[]]})).await {
+ Ok(v) => v,
+ Err(e) => {
+ println!("Error setting doc: {e}");
+ return;
+ }
+ };
+
+ match r.json_get("doc", "$.a[?@ subsetof [1,2,3]]").await {
+ Ok(res13) => {
+ let res13: String = res13;
+ println!("{res13}"); // >>> [[1,2],[]]
+ // REMOVE_START
+ assert_eq!(res13, "[[1,2],[]]");
+ // REMOVE_END
+ },
+ Err(e) => {
+ println!("Error getting doc: {e}");
+ return;
+ }
+ }
+
+ let _: bool = match r.json_set("doc", "$", &json!({"a":[[1,9],[8,9],[]]})).await {
+ Ok(v) => v,
+ Err(e) => {
+ println!("Error setting doc: {e}");
+ return;
+ }
+ };
+
+ match r.json_get("doc", "$.a[?@ anyof [1,2,3]]").await {
+ Ok(res14) => {
+ let res14: String = res14;
+ println!("{res14}"); // >>> [[1,9]]
+ // REMOVE_START
+ assert_eq!(res14, "[[1,9]]");
+ // REMOVE_END
+ },
+ Err(e) => {
+ println!("Error getting doc: {e}");
+ return;
+ }
+ }
+
+ let _: bool = match r.json_set("doc", "$", &json!({"a":[[4,5],[1,9],[]]})).await {
+ Ok(v) => v,
+ Err(e) => {
+ println!("Error setting doc: {e}");
+ return;
+ }
+ };
+
+ match r.json_get("doc", "$.a[?@ noneof [1,2,3]]").await {
+ Ok(res15) => {
+ let res15: String = res15;
+ println!("{res15}"); // >>> [[4,5],[]]
+ // REMOVE_START
+ assert_eq!(res15, "[[4,5],[]]");
+ // REMOVE_END
+ },
+ Err(e) => {
+ println!("Error getting doc: {e}");
+ return;
+ }
+ }
+
+ // REMOVE_START
+ let _: Result = r.del("doc").await;
+ // REMOVE_END
+ // STEP_END
+
+ // STEP_START filter_size_empty
+ let _: bool = match r.json_set("doc", "$", &json!({"a":[[4,5],[1],[7,8,9]]})).await {
+ Ok(v) => v,
+ Err(e) => {
+ println!("Error setting doc: {e}");
+ return;
+ }
+ };
+
+ match r.json_get("doc", "$.a[?@ sizeof 2]").await {
+ Ok(res16) => {
+ let res16: String = res16;
+ println!("{res16}"); // >>> [[4,5]]
+ // REMOVE_START
+ assert_eq!(res16, "[[4,5]]");
+ // REMOVE_END
+ },
+ Err(e) => {
+ println!("Error getting doc: {e}");
+ return;
+ }
+ }
+
+ let _: bool = match r.json_set("doc", "$", &json!({"a":[[],[1],"",[2,3],{},{"k":1}]})).await {
+ Ok(v) => v,
+ Err(e) => {
+ println!("Error setting doc: {e}");
+ return;
+ }
+ };
+
+ match r.json_get("doc", "$.a[?@ empty true]").await {
+ Ok(res17) => {
+ let res17: String = res17;
+ println!("{res17}"); // >>> [[],"",{}]
+ // REMOVE_START
+ assert_eq!(res17, r#"[[],"",{}]"#);
+ // REMOVE_END
+ },
+ Err(e) => {
+ println!("Error getting doc: {e}");
+ return;
+ }
+ }
+
+ match r.json_get("doc", "$.a[?@ empty false]").await {
+ Ok(res18) => {
+ let res18: String = res18;
+ println!("{res18}"); // >>> [[1],[2,3],{"k":1}]
+ // REMOVE_START
+ assert_eq!(res18, r#"[[1],[2,3],{"k":1}]"#);
+ // REMOVE_END
+ },
+ Err(e) => {
+ println!("Error getting doc: {e}");
+ return;
+ }
+ }
+
+ // REMOVE_START
+ let _: Result = r.del("doc").await;
+ // REMOVE_END
+ // STEP_END
+
+ // STEP_START filter_getkeys
+ let _: bool = match r.json_set(
+ "doc",
+ "$",
+ &json!({"obj":{"x":1,"y":2},"books":[{"t":"a"},{"t":"b"}]}),
+ ).await {
+ Ok(v) => v,
+ Err(e) => {
+ println!("Error setting doc: {e}");
+ return;
+ }
+ };
+
+ match r.json_get("doc", "$.obj~").await {
+ Ok(res19) => {
+ let res19: String = res19;
+ println!("{res19}"); // >>> ["x","y"]
+ // REMOVE_START
+ assert_eq!(res19, r#"["x","y"]"#);
+ // REMOVE_END
+ },
+ Err(e) => {
+ println!("Error getting doc: {e}");
+ return;
+ }
+ }
+
+ match r.json_get("doc", "$~").await {
+ Ok(res20) => {
+ let res20: String = res20;
+ println!("{res20}"); // >>> ["obj","books"]
+ // REMOVE_START
+ assert_eq!(res20, r#"["obj","books"]"#);
+ // REMOVE_END
+ },
+ Err(e) => {
+ println!("Error getting doc: {e}");
+ return;
+ }
+ }
+
+ match r.json_get("doc", "$.books~").await {
+ Ok(res21) => {
+ let res21: String = res21;
+ println!("{res21}"); // >>> []
+ // REMOVE_START
+ assert_eq!(res21, "[]");
+ // REMOVE_END
+ },
+ Err(e) => {
+ println!("Error getting doc: {e}");
+ return;
+ }
+ }
+
+ // REMOVE_START
+ let _: Result = r.del("doc").await;
+ // REMOVE_END
+ // STEP_END
+
+ }
+}
diff --git a/local_examples/json_path_ops/rust-sync/json_path_ops.rs b/local_examples/json_path_ops/rust-sync/json_path_ops.rs
new file mode 100644
index 0000000000..b9e38ebdef
--- /dev/null
+++ b/local_examples/json_path_ops/rust-sync/json_path_ops.rs
@@ -0,0 +1,513 @@
+// EXAMPLE: json_path_ops
+#[cfg(test)]
+mod json_path_ops_tests {
+ use redis::{Commands, JsonCommands};
+ use serde_json::json;
+
+ #[test]
+ fn run() {
+ let mut r = match redis::Client::open("redis://127.0.0.1") {
+ Ok(client) => {
+ match client.get_connection() {
+ Ok(conn) => conn,
+ Err(e) => {
+ println!("Failed to connect to Redis: {e}");
+ return;
+ }
+ }
+ },
+ Err(e) => {
+ println!("Failed to create Redis client: {e}");
+ return;
+ }
+ };
+
+ // Clean up any existing data
+ let _: Result = r.del("doc");
+
+ // STEP_START filter_negation
+ match r.json_set("doc", "$", &json!([{"a":1,"b":1},{"b":2},{"a":1},{"c":3}])) {
+ Ok(res1) => {
+ let res1: bool = res1;
+ println!("{}", if res1 { "OK" } else { "(nil)" }); // >>> OK
+ // REMOVE_START
+ assert!(res1);
+ // REMOVE_END
+ },
+ Err(e) => {
+ println!("Error setting doc: {e}");
+ return;
+ }
+ }
+
+ match r.json_get("doc", "$[?!@.a]") {
+ Ok(res2) => {
+ let res2: String = res2;
+ println!("{res2}"); // >>> [{"b":2},{"c":3}]
+ // REMOVE_START
+ assert_eq!(res2, r#"[{"b":2},{"c":3}]"#);
+ // REMOVE_END
+ },
+ Err(e) => {
+ println!("Error getting doc: {e}");
+ return;
+ }
+ }
+
+ match r.json_get("doc", "$[?!(@.a==1)]") {
+ Ok(res3) => {
+ let res3: String = res3;
+ println!("{res3}"); // >>> [{"b":2},{"c":3}]
+ // REMOVE_START
+ assert_eq!(res3, r#"[{"b":2},{"c":3}]"#);
+ // REMOVE_END
+ },
+ Err(e) => {
+ println!("Error getting doc: {e}");
+ return;
+ }
+ }
+
+ match r.json_get("doc", "$[?!@.a && @.b]") {
+ Ok(res4) => {
+ let res4: String = res4;
+ println!("{res4}"); // >>> [{"b":2}]
+ // REMOVE_START
+ assert_eq!(res4, r#"[{"b":2}]"#);
+ // REMOVE_END
+ },
+ Err(e) => {
+ println!("Error getting doc: {e}");
+ return;
+ }
+ }
+
+ // REMOVE_START
+ let _: Result = r.del("doc");
+ // REMOVE_END
+ // STEP_END
+
+ // STEP_START filter_literal_eq
+ match r.json_set(
+ "doc",
+ "$",
+ &json!({"arrs":[[1],[2],[1,2],[1,[2]]],"objs":[{"x":1},{"x":2},{"y":1}]}),
+ ) {
+ Ok(res5) => {
+ let res5: bool = res5;
+ println!("{}", if res5 { "OK" } else { "(nil)" }); // >>> OK
+ // REMOVE_START
+ assert!(res5);
+ // REMOVE_END
+ },
+ Err(e) => {
+ println!("Error setting doc: {e}");
+ return;
+ }
+ }
+
+ match r.json_get("doc", "$.arrs[?(@ == [1])]") {
+ Ok(res6) => {
+ let res6: String = res6;
+ println!("{res6}"); // >>> [[1]]
+ // REMOVE_START
+ assert_eq!(res6, "[[1]]");
+ // REMOVE_END
+ },
+ Err(e) => {
+ println!("Error getting doc: {e}");
+ return;
+ }
+ }
+
+ match r.json_get("doc", "$.arrs[?(@ == [1,[2]])]") {
+ Ok(res7) => {
+ let res7: String = res7;
+ println!("{res7}"); // >>> [[1,[2]]]
+ // REMOVE_START
+ assert_eq!(res7, "[[1,[2]]]");
+ // REMOVE_END
+ },
+ Err(e) => {
+ println!("Error getting doc: {e}");
+ return;
+ }
+ }
+
+ match r.json_get("doc", r#"$.objs[?(@ == {"x":1})]"#) {
+ Ok(res8) => {
+ let res8: String = res8;
+ println!("{res8}"); // >>> [{"x":1}]
+ // REMOVE_START
+ assert_eq!(res8, r#"[{"x":1}]"#);
+ // REMOVE_END
+ },
+ Err(e) => {
+ println!("Error getting doc: {e}");
+ return;
+ }
+ }
+
+ // REMOVE_START
+ let _: Result = r.del("doc");
+ // REMOVE_END
+ // STEP_END
+
+ // STEP_START filter_arithmetic
+ match r.json_set("doc", "$", &json!([{"a":2,"b":3},{"a":5,"b":2}])) {
+ Ok(res9) => {
+ let res9: bool = res9;
+ println!("{}", if res9 { "OK" } else { "(nil)" }); // >>> OK
+ // REMOVE_START
+ assert!(res9);
+ // REMOVE_END
+ },
+ Err(e) => {
+ println!("Error setting doc: {e}");
+ return;
+ }
+ }
+
+ match r.json_get("doc", "$[?@.a + 1 == 3]") {
+ Ok(res10) => {
+ let res10: String = res10;
+ println!("{res10}"); // >>> [{"a":2,"b":3}]
+ // REMOVE_START
+ assert_eq!(res10, r#"[{"a":2,"b":3}]"#);
+ // REMOVE_END
+ },
+ Err(e) => {
+ println!("Error getting doc: {e}");
+ return;
+ }
+ }
+
+ match r.json_get("doc", "$[?@.a + @.b * 2 == 8]") {
+ Ok(res11) => {
+ let res11: String = res11;
+ println!("{res11}"); // >>> [{"a":2,"b":3}]
+ // REMOVE_START
+ assert_eq!(res11, r#"[{"a":2,"b":3}]"#);
+ // REMOVE_END
+ },
+ Err(e) => {
+ println!("Error getting doc: {e}");
+ return;
+ }
+ }
+
+ match r.json_get("doc", "$[?(@.a + @.b) * 2 == 10]") {
+ Ok(res12) => {
+ let res12: String = res12;
+ println!("{res12}"); // >>> [{"a":2,"b":3}]
+ // REMOVE_START
+ assert_eq!(res12, r#"[{"a":2,"b":3}]"#);
+ // REMOVE_END
+ },
+ Err(e) => {
+ println!("Error getting doc: {e}");
+ return;
+ }
+ }
+
+ // REMOVE_START
+ let _: Result = r.del("doc");
+ // REMOVE_END
+ // STEP_END
+
+ // STEP_START filter_membership
+ match r.json_set("doc", "$", &json!({"a":[1,2,3,4],"allow":[2,3]})) {
+ Ok(res13) => {
+ let res13: bool = res13;
+ println!("{}", if res13 { "OK" } else { "(nil)" }); // >>> OK
+ // REMOVE_START
+ assert!(res13);
+ // REMOVE_END
+ },
+ Err(e) => {
+ println!("Error setting doc: {e}");
+ return;
+ }
+ }
+
+ match r.json_get("doc", "$.a[?@ in [2,4]]") {
+ Ok(res14) => {
+ let res14: String = res14;
+ println!("{res14}"); // >>> [2,4]
+ // REMOVE_START
+ assert_eq!(res14, "[2,4]");
+ // REMOVE_END
+ },
+ Err(e) => {
+ println!("Error getting doc: {e}");
+ return;
+ }
+ }
+
+ match r.json_get("doc", "$.a[?@ nin [2,4]]") {
+ Ok(res15) => {
+ let res15: String = res15;
+ println!("{res15}"); // >>> [1,3]
+ // REMOVE_START
+ assert_eq!(res15, "[1,3]");
+ // REMOVE_END
+ },
+ Err(e) => {
+ println!("Error getting doc: {e}");
+ return;
+ }
+ }
+
+ match r.json_get("doc", "$.a[?@ in $.allow]") {
+ Ok(res16) => {
+ let res16: String = res16;
+ println!("{res16}"); // >>> [2,3]
+ // REMOVE_START
+ assert_eq!(res16, "[2,3]");
+ // REMOVE_END
+ },
+ Err(e) => {
+ println!("Error getting doc: {e}");
+ return;
+ }
+ }
+
+ // REMOVE_START
+ let _: Result = r.del("doc");
+ // REMOVE_END
+ // STEP_END
+
+ // STEP_START filter_set_relations
+ match r.json_set("doc", "$", &json!({"a":[[1,2],[1,5],[]]})) {
+ Ok(res17) => {
+ let res17: bool = res17;
+ println!("{}", if res17 { "OK" } else { "(nil)" }); // >>> OK
+ // REMOVE_START
+ assert!(res17);
+ // REMOVE_END
+ },
+ Err(e) => {
+ println!("Error setting doc: {e}");
+ return;
+ }
+ }
+
+ match r.json_get("doc", "$.a[?@ subsetof [1,2,3]]") {
+ Ok(res18) => {
+ let res18: String = res18;
+ println!("{res18}"); // >>> [[1,2],[]]
+ // REMOVE_START
+ assert_eq!(res18, "[[1,2],[]]");
+ // REMOVE_END
+ },
+ Err(e) => {
+ println!("Error getting doc: {e}");
+ return;
+ }
+ }
+
+ match r.json_set("doc", "$", &json!({"a":[[1,9],[8,9],[]]})) {
+ Ok(res19) => {
+ let res19: bool = res19;
+ println!("{}", if res19 { "OK" } else { "(nil)" }); // >>> OK
+ // REMOVE_START
+ assert!(res19);
+ // REMOVE_END
+ },
+ Err(e) => {
+ println!("Error setting doc: {e}");
+ return;
+ }
+ }
+
+ match r.json_get("doc", "$.a[?@ anyof [1,2,3]]") {
+ Ok(res20) => {
+ let res20: String = res20;
+ println!("{res20}"); // >>> [[1,9]]
+ // REMOVE_START
+ assert_eq!(res20, "[[1,9]]");
+ // REMOVE_END
+ },
+ Err(e) => {
+ println!("Error getting doc: {e}");
+ return;
+ }
+ }
+
+ match r.json_set("doc", "$", &json!({"a":[[4,5],[1,9],[]]})) {
+ Ok(res21) => {
+ let res21: bool = res21;
+ println!("{}", if res21 { "OK" } else { "(nil)" }); // >>> OK
+ // REMOVE_START
+ assert!(res21);
+ // REMOVE_END
+ },
+ Err(e) => {
+ println!("Error setting doc: {e}");
+ return;
+ }
+ }
+
+ match r.json_get("doc", "$.a[?@ noneof [1,2,3]]") {
+ Ok(res22) => {
+ let res22: String = res22;
+ println!("{res22}"); // >>> [[4,5],[]]
+ // REMOVE_START
+ assert_eq!(res22, "[[4,5],[]]");
+ // REMOVE_END
+ },
+ Err(e) => {
+ println!("Error getting doc: {e}");
+ return;
+ }
+ }
+
+ // REMOVE_START
+ let _: Result = r.del("doc");
+ // REMOVE_END
+ // STEP_END
+
+ // STEP_START filter_size_empty
+ match r.json_set("doc", "$", &json!({"a":[[4,5],[1],[7,8,9]]})) {
+ Ok(res23) => {
+ let res23: bool = res23;
+ println!("{}", if res23 { "OK" } else { "(nil)" }); // >>> OK
+ // REMOVE_START
+ assert!(res23);
+ // REMOVE_END
+ },
+ Err(e) => {
+ println!("Error setting doc: {e}");
+ return;
+ }
+ }
+
+ match r.json_get("doc", "$.a[?@ sizeof 2]") {
+ Ok(res24) => {
+ let res24: String = res24;
+ println!("{res24}"); // >>> [[4,5]]
+ // REMOVE_START
+ assert_eq!(res24, "[[4,5]]");
+ // REMOVE_END
+ },
+ Err(e) => {
+ println!("Error getting doc: {e}");
+ return;
+ }
+ }
+
+ match r.json_set("doc", "$", &json!({"a":[[],[1],"",[2,3],{},{"k":1}]})) {
+ Ok(res25) => {
+ let res25: bool = res25;
+ println!("{}", if res25 { "OK" } else { "(nil)" }); // >>> OK
+ // REMOVE_START
+ assert!(res25);
+ // REMOVE_END
+ },
+ Err(e) => {
+ println!("Error setting doc: {e}");
+ return;
+ }
+ }
+
+ match r.json_get("doc", "$.a[?@ empty true]") {
+ Ok(res26) => {
+ let res26: String = res26;
+ println!("{res26}"); // >>> [[],"",{}]
+ // REMOVE_START
+ assert_eq!(res26, r#"[[],"",{}]"#);
+ // REMOVE_END
+ },
+ Err(e) => {
+ println!("Error getting doc: {e}");
+ return;
+ }
+ }
+
+ match r.json_get("doc", "$.a[?@ empty false]") {
+ Ok(res27) => {
+ let res27: String = res27;
+ println!("{res27}"); // >>> [[1],[2,3],{"k":1}]
+ // REMOVE_START
+ assert_eq!(res27, r#"[[1],[2,3],{"k":1}]"#);
+ // REMOVE_END
+ },
+ Err(e) => {
+ println!("Error getting doc: {e}");
+ return;
+ }
+ }
+
+ // REMOVE_START
+ let _: Result = r.del("doc");
+ // REMOVE_END
+ // STEP_END
+
+ // STEP_START filter_getkeys
+ match r.json_set(
+ "doc",
+ "$",
+ &json!({"obj":{"x":1,"y":2},"books":[{"t":"a"},{"t":"b"}]}),
+ ) {
+ Ok(res28) => {
+ let res28: bool = res28;
+ println!("{}", if res28 { "OK" } else { "(nil)" }); // >>> OK
+ // REMOVE_START
+ assert!(res28);
+ // REMOVE_END
+ },
+ Err(e) => {
+ println!("Error setting doc: {e}");
+ return;
+ }
+ }
+
+ match r.json_get("doc", "$.obj~") {
+ Ok(res29) => {
+ let res29: String = res29;
+ println!("{res29}"); // >>> ["x","y"]
+ // REMOVE_START
+ assert_eq!(res29, r#"["x","y"]"#);
+ // REMOVE_END
+ },
+ Err(e) => {
+ println!("Error getting doc: {e}");
+ return;
+ }
+ }
+
+ match r.json_get("doc", "$~") {
+ Ok(res30) => {
+ let res30: String = res30;
+ println!("{res30}"); // >>> ["obj","books"]
+ // REMOVE_START
+ assert_eq!(res30, r#"["obj","books"]"#);
+ // REMOVE_END
+ },
+ Err(e) => {
+ println!("Error getting doc: {e}");
+ return;
+ }
+ }
+
+ match r.json_get("doc", "$.books~") {
+ Ok(res31) => {
+ let res31: String = res31;
+ println!("{res31}"); // >>> []
+ // REMOVE_START
+ assert_eq!(res31, "[]");
+ // REMOVE_END
+ },
+ Err(e) => {
+ println!("Error getting doc: {e}");
+ return;
+ }
+ }
+
+ // REMOVE_START
+ let _: Result = r.del("doc");
+ // REMOVE_END
+ // STEP_END
+
+ }
+}