Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions content/develop/data-types/json/path.md
Original file line number Diff line number Diff line change
Expand Up @@ -554,7 +554,7 @@ The top level of a JSONPath query can be an expression that *computes* a value

A projection that evaluates to *Nothing* produces an empty reply (`[]`).

```
{{< clients-example set="json_path_ops" step="proj_basic" description="Projection expressions: Use arithmetic, a function call, or the length of a path as a top-level query that computes a value instead of only selecting nodes" difficulty="advanced" >}}
> JSON.SET doc $ '{"a":2,"b":4,"arr":[1,2,3]}'
OK
> JSON.GET doc '$.a + 1'
Expand All @@ -567,14 +567,14 @@ OK
"[3]"
> JSON.GET doc '$.a / 0'
"[]"
```
{{< /clients-example >}}

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:
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. The order of keys in that object is not guaranteed.

```
{{< clients-example set="json_path_ops" step="proj_multipath" description="Multi-path queries: Pass more than one path to JSON.GET to have each path's result returned under its own key in a single reply" difficulty="advanced" >}}
> JSON.GET doc '$.a + 1' '$.b'
"{\"$.a + 1\":[3],\"$.b\":[4]}"
```
{{< /clients-example >}}

[`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.

Expand Down
126 changes: 126 additions & 0 deletions local_examples/json_path_ops/go-redis/json_path_ops_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ package example_commands_test

import (
"context"
"encoding/json"
"fmt"

"github.com/redis/go-redis/v9"
Expand Down Expand Up @@ -948,3 +949,128 @@ func ExampleClient_json_path_ops_func_append() {
// OK
// [{"t":"a","price":30},{"t":"X"}]
}

func ExampleClient_json_path_ops_proj_basic() {
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 proj_basic
res1, err := rdb.JSONSet(ctx, "doc", "$",
`{"a":2,"b":4,"arr":[1,2,3]}`,
).Result()

if err != nil {
panic(err)
}

fmt.Println(res1) // >>> OK

res2, err := rdb.JSONGet(ctx, "doc", `$.a + 1`).Result()

if err != nil {
panic(err)
}

fmt.Println(res2) // >>> [3]

res3, err := rdb.JSONGet(ctx, "doc", `$.a * $.b`).Result()

if err != nil {
panic(err)
}

fmt.Println(res3) // >>> [8]

res4, err := rdb.JSONGet(ctx, "doc", `($.a + $.b) / 2`).Result()

if err != nil {
panic(err)
}

fmt.Println(res4) // >>> [3.0]

res5, err := rdb.JSONGet(ctx, "doc", `$.arr.length()`).Result()

if err != nil {
panic(err)
}

fmt.Println(res5) // >>> [3]

res6, err := rdb.JSONGet(ctx, "doc", `$.a / 0`).Result()

if err != nil {
panic(err)
}

fmt.Println(res6) // >>> []
// STEP_END

// Output:
// OK
// [3]
// [8]
// [3.0]
// [3]
// []
}

func ExampleClient_json_path_ops_proj_multipath() {
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 proj_multipath
res1, err := rdb.JSONSet(ctx, "doc", "$",
`{"a":2,"b":4,"arr":[1,2,3]}`,
).Result()

if err != nil {
panic(err)
}

fmt.Println(res1) // >>> OK

raw, err := rdb.JSONGet(ctx, "doc", `$.a + 1`, `$.b`).Result()

if err != nil {
panic(err)
}

// A multi-path JSON.GET reply is a JSON object whose key order is not
// guaranteed, so decode it into a map rather than printing the raw
// string. Go's fmt package prints map keys in sorted order, which keeps
// this example's output deterministic regardless of the order Redis
// returns the paths in.
res2 := map[string]interface{}{}

if err := json.Unmarshal([]byte(raw), &res2); err != nil {
panic(err)
}

fmt.Println(res2) // >>> map[$.a + 1:[3] $.b:[4]]
// STEP_END

// Output:
// OK
// map[$.a + 1:[3] $.b:[4]]
}
38 changes: 38 additions & 0 deletions local_examples/json_path_ops/jedis/JsonPathOpsExample.java
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,44 @@ public void run() {
jedis.del("doc");
// REMOVE_END

// STEP_START proj_basic
String res64 = jedis.jsonSet("doc", new Path2("$"), "{\"a\":2,\"b\":4,\"arr\":[1,2,3]}");
System.out.println(res64); // >>> OK

Object res65 = jedis.jsonGet("doc", new Path2("$.a + 1"));
System.out.println(res65); // >>> [3]

Object res66 = jedis.jsonGet("doc", new Path2("$.a * $.b"));
System.out.println(res66); // >>> [8]

Object res67 = jedis.jsonGet("doc", new Path("($.a + $.b) / 2"));
System.out.println(res67); // >>> [3.0]

Object res68 = jedis.jsonGet("doc", new Path2("$.arr.length()"));
System.out.println(res68); // >>> [3]

Object res69 = jedis.jsonGet("doc", new Path2("$.a / 0"));
System.out.println(res69); // >>> []
// STEP_END
// REMOVE_START
assertEquals("[3]", res65.toString());
assertEquals("[8]", res66.toString());
assertEquals("[3.0]", res67.toString());
assertEquals("[3]", res68.toString());
assertEquals("[]", res69.toString());
// REMOVE_END

// STEP_START proj_multipath
String res70 = jedis.jsonSet("doc", new Path2("$"), "{\"a\":2,\"b\":4,\"arr\":[1,2,3]}");
System.out.println(res70); // >>> OK

Object res71 = jedis.jsonGet("doc", new Path2("$.a + 1"), new Path2("$.b"));
System.out.println(res71); // >>> {"$.a + 1":[3],"$.b":[4]} (a JSON object; key order is not guaranteed)
// STEP_END
// REMOVE_START
assertTrue(((JSONObject) res71).similar(new JSONObject("{\"$.a + 1\":[3],\"$.b\":[4]}")));
jedis.del("doc");
// REMOVE_END

// HIDE_START
jedis.close();
Expand Down
75 changes: 75 additions & 0 deletions local_examples/json_path_ops/lettuce-async/JsonPathOpsExample.java
Original file line number Diff line number Diff line change
Expand Up @@ -603,6 +603,81 @@ public void run() {
asyncCommands.del("doc").toCompletableFuture().join();
// REMOVE_END

// STEP_START proj_basic
CompletableFuture<Void> projBasicExample = asyncCommands
.jsonSet("doc", JsonPath.ROOT_PATH,
parser.createJsonValue("{\"a\":2,\"b\":4,\"arr\":[1,2,3]}"))
.thenCompose(res1 -> {
System.out.println(res1); // >>> OK
// REMOVE_START
assertThat(res1).isEqualTo("OK");
// REMOVE_END
return asyncCommands.jsonGet("doc", JsonPath.of("$.a + 1"));
}).thenCompose(res2 -> {
System.out.println(res2); // >>> [[3]]
// REMOVE_START
assertThat(res2.toString()).isEqualTo("[[3]]");
// REMOVE_END
return asyncCommands.jsonGet("doc", JsonPath.of("$.a * $.b"));
}).thenCompose(res3 -> {
System.out.println(res3); // >>> [[8]]
// REMOVE_START
assertThat(res3.toString()).isEqualTo("[[8]]");
// REMOVE_END
return asyncCommands.jsonGet("doc", JsonPath.of("($.a + $.b) / 2"));
}).thenCompose(res4 -> {
System.out.println(res4); // >>> [[3.0]]
// REMOVE_START
assertThat(res4.toString()).isEqualTo("[[3.0]]");
// REMOVE_END
return asyncCommands.jsonGet("doc", JsonPath.of("$.arr.length()"));
}).thenCompose(res5 -> {
System.out.println(res5); // >>> [[3]]
// REMOVE_START
assertThat(res5.toString()).isEqualTo("[[3]]");
// REMOVE_END
return asyncCommands.jsonGet("doc", JsonPath.of("$.a / 0"));
}).thenAccept(res6 -> {
System.out.println(res6); // >>> [[]]
// REMOVE_START
assertThat(res6.toString()).isEqualTo("[[]]");
// REMOVE_END
}).toCompletableFuture();
// STEP_END

projBasicExample.join();
// REMOVE_START
asyncCommands.del("doc").toCompletableFuture().join();
// REMOVE_END

// STEP_START proj_multipath
CompletableFuture<Void> projMultipathExample = asyncCommands
.jsonSet("doc", JsonPath.ROOT_PATH,
parser.createJsonValue("{\"a\":2,\"b\":4,\"arr\":[1,2,3]}"))
.thenCompose(res7 -> {
System.out.println(res7); // >>> OK
// REMOVE_START
assertThat(res7).isEqualTo("OK");
// REMOVE_END
return asyncCommands.jsonGet("doc", JsonPath.of("$.a + 1"), JsonPath.of("$.b"));
}).thenAccept(res8 -> {
// The multi-path JSON.GET reply is a single JSON object keyed by path,
// and Redis does not guarantee key order, so look up each path rather
// than comparing the object's rendered string.
JsonObject paths = res8.get(0).asJsonObject();
System.out.println(paths); // >>> {"$.a + 1":[3],"$.b":[4]}
// REMOVE_START
assertThat(paths.size()).isEqualTo(2);
assertThat(paths.get("$.a + 1").toString()).isEqualTo("[3]");
assertThat(paths.get("$.b").toString()).isEqualTo("[4]");
// REMOVE_END
}).toCompletableFuture();
// STEP_END

projMultipathExample.join();
// REMOVE_START
asyncCommands.del("doc").toCompletableFuture().join();
// REMOVE_END
} finally {
redisClient.shutdown();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -660,6 +660,89 @@ public void run() {
reactiveCommands.del("doc").block();
// REMOVE_END

// STEP_START proj_basic
Mono<Void> projBasicExample = reactiveCommands
.jsonSet("doc", JsonPath.ROOT_PATH, parser.createJsonValue("{\"a\":2,\"b\":4,\"arr\":[1,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 + 1")).collectList())
.doOnNext(res2 -> {
System.out.println(res2); // >>> [[3]]
// REMOVE_START
assertThat(res2.toString()).isEqualTo("[[3]]");
// REMOVE_END
})
.flatMap(res2 -> reactiveCommands.jsonGet("doc", JsonPath.of("$.a * $.b")).collectList())
.doOnNext(res3 -> {
System.out.println(res3); // >>> [[8]]
// REMOVE_START
assertThat(res3.toString()).isEqualTo("[[8]]");
// REMOVE_END
})
.flatMap(res3 -> reactiveCommands.jsonGet("doc", JsonPath.of("($.a + $.b) / 2")).collectList())
.doOnNext(res4 -> {
System.out.println(res4); // >>> [[3.0]]
// REMOVE_START
assertThat(res4.toString()).isEqualTo("[[3.0]]");
// REMOVE_END
})
.flatMap(res4 -> reactiveCommands.jsonGet("doc", JsonPath.of("$.arr.length()")).collectList())
.doOnNext(res5 -> {
System.out.println(res5); // >>> [[3]]
// REMOVE_START
assertThat(res5.toString()).isEqualTo("[[3]]");
// REMOVE_END
})
.flatMap(res5 -> reactiveCommands.jsonGet("doc", JsonPath.of("$.a / 0")).collectList())
.doOnNext(res6 -> {
System.out.println(res6); // >>> [[]]
// REMOVE_START
assertThat(res6.toString()).isEqualTo("[[]]");
// REMOVE_END
})
.then();
// STEP_END

projBasicExample.block();
// REMOVE_START
reactiveCommands.del("doc").block();
// REMOVE_END

// STEP_START proj_multipath
Mono<Void> projMultipathExample = reactiveCommands
.jsonSet("doc", JsonPath.ROOT_PATH, parser.createJsonValue("{\"a\":2,\"b\":4,\"arr\":[1,2,3]}"))
.doOnNext(res7 -> {
System.out.println(res7); // >>> OK
// REMOVE_START
assertThat(res7).isEqualTo("OK");
// REMOVE_END
})
.flatMap(res7 -> reactiveCommands.jsonGet("doc", JsonPath.of("$.a + 1"), JsonPath.of("$.b"))
.collectList())
.doOnNext(res8 -> {
System.out.println(res8); // >>> [{"$.a + 1":[3],"$.b":[4]}]
// REMOVE_START
// JSON.GET's multi-path reply key order is not guaranteed by the
// protocol, so compare the parsed object's entries rather than
// the serialized string.
assertThat(res8).hasSize(1);
JsonObject multiPathResult = res8.get(0).asJsonObject();
assertThat(multiPathResult.size()).isEqualTo(2);
assertThat(multiPathResult.get("$.a + 1").toString()).isEqualTo("[3]");
assertThat(multiPathResult.get("$.b").toString()).isEqualTo("[4]");
// REMOVE_END
})
.then();
// STEP_END

projMultipathExample.block();
// REMOVE_START
reactiveCommands.del("doc").block();
// REMOVE_END
} finally {
redisClient.shutdown();
}
Expand Down
Loading
Loading