Repository: redis/lettuce
Pull request #3201 (2025-02-28 22:15:02)

Some json command assume optional path while it's not (#3198)
<!--
Thank you for proposing a pull request. This template will guide you through the essential steps necessary for a pull request.
-->
Port #3200 to 6.5.x

Make sure that:

- [x] You have read the [contribution guidelines](https://github.com/lettuce-io/lettuce-core/blob/main/.github/CONTRIBUTING.md).
- [x] You have created a feature request first to discuss your contribution intent. Please reference the feature request ticket number in the pull request.
- [x] You applied code formatting rules using the `mvn formatter:format` target. Don’t submit any formatting related changes.
- [x] You submit test cases (unit or integration tests) that back your changes.

 <!--
Great! Live long and prosper.
-->



Patch:
diff --git a/src/main/java/io/lettuce/core/RedisJsonCommandBuilder.java b/src/main/java/io/lettuce/core/RedisJsonCommandBuilder.java
index 9aff2e594..bd713854c 100644
--- a/src/main/java/io/lettuce/core/RedisJsonCommandBuilder.java
+++ b/src/main/java/io/lettuce/core/RedisJsonCommandBuilder.java
@@ -47,6 +47,7 @@ class RedisJsonCommandBuilder<K, V> extends BaseRedisCommandBuilder<K, V> {
         CommandArgs<K, V> args = new CommandArgs<>(codec).addKey(key);
 
         if (jsonPath != null && !jsonPath.isRootPath()) {
+            // OPTIONAL as per API
             args.add(jsonPath.toString());
         }
 
@@ -62,10 +63,7 @@ class RedisJsonCommandBuilder<K, V> extends BaseRedisCommandBuilder<K, V> {
 
         CommandArgs<K, V> args = new CommandArgs<>(codec).addKey(key);
 
-        if (jsonPath != null && !jsonPath.isRootPath()) {
-            args.add(jsonPath.toString());
-        }
-
+        args.add(jsonPath.toString());
         args.add(value.asByteBuffer().array());
 
         if (range != null) {
@@ -81,10 +79,7 @@ class RedisJsonCommandBuilder<K, V> extends BaseRedisCommandBuilder<K, V> {
 
         CommandArgs<K, V> args = new CommandArgs<>(codec).addKey(key);
 
-        if (jsonPath != null && !jsonPath.isRootPath()) {
-            args.add(jsonPath.toString());
-        }
-
+        args.add(jsonPath.toString());
         args.add(index);
 
         for (JsonValue value : values) {
@@ -100,6 +95,7 @@ class RedisJsonCommandBuilder<K, V> extends BaseRedisCommandBuilder<K, V> {
         CommandArgs<K, V> args = new CommandArgs<>(codec).addKey(key);
 
         if (jsonPath != null && !jsonPath.isRootPath()) {
+            // OPTIONAL as per API
             args.add(jsonPath.toString());
         }
         return createCommand(JSON_ARRLEN, (CommandOutput) new ArrayOutput<>(codec), args);
@@ -110,10 +106,12 @@ class RedisJsonCommandBuilder<K, V> extends BaseRedisCommandBuilder<K, V> {
 
         CommandArgs<K, V> args = new CommandArgs<>(codec).addKey(key);
 
-        if (jsonPath != null) {
+        if (jsonPath != null && !jsonPath.isRootPath()) {
+            // OPTIONAL as per API
             args.add(jsonPath.toString());
 
             if (index != -1) {
+                // OPTIONAL as per API
                 args.add(index);
             }
         }
@@ -126,10 +124,7 @@ class RedisJsonCommandBuilder<K, V> extends BaseRedisCommandBuilder<K, V> {
         notNullKey(key);
 
         CommandArgs<K, V> args = new CommandArgs<>(codec).addKey(key);
-
-        if (jsonPath != null && !jsonPath.isRootPath()) {
-            args.add(jsonPath.toString());
-        }
+        args.add(jsonPath.toString());
 
         if (range != null) {
             range.build(args);
@@ -144,6 +139,7 @@ class RedisJsonCommandBuilder<K, V> extends BaseRedisCommandBuilder<K, V> {
         CommandArgs<K, V> args = new CommandArgs<>(codec).addKey(key);
 
         if (jsonPath != null && !jsonPath.isRootPath()) {
+            // OPTIONAL as per API
             args.add(jsonPath.toString());
         }
 
@@ -156,10 +152,12 @@ class RedisJsonCommandBuilder<K, V> extends BaseRedisCommandBuilder<K, V> {
         CommandArgs<K, V> args = new CommandArgs<>(codec).addKey(key);
 
         if (options != null) {
+            // OPTIONAL as per API
             options.build(args);
         }
 
         if (jsonPaths != null) {
+            // OPTIONAL as per API
             for (JsonPath jsonPath : jsonPaths) {
                 if (jsonPath != null) {
                     args.add(jsonPath.toString());
@@ -175,11 +173,7 @@ class RedisJsonCommandBuilder<K, V> extends BaseRedisCommandBuilder<K, V> {
         notNullKey(key);
 
         CommandArgs<K, V> args = new CommandArgs<>(codec).addKey(key);
-
-        if (jsonPath != null && !jsonPath.isRootPath()) {
-            args.add(jsonPath.toString());
-        }
-
+        args.add(jsonPath.toString());
         args.add(value.asByteBuffer().array());
 
         return createCommand(JSON_MERGE, new StatusOutput<>(codec), args);
@@ -189,10 +183,7 @@ class RedisJsonCommandBuilder<K, V> extends BaseRedisCommandBuilder<K, V> {
         notEmpty(keys);
 
         CommandArgs<K, V> args = new CommandArgs<>(codec).addKeys(keys);
-
-        if (jsonPath != null) {
-            args.add(jsonPath.toString());
-        }
+        args.add(jsonPath.toString());
 
         return createCommand(JSON_MGET, new JsonValueListOutput<>(codec, parser.get()), args);
     }
@@ -214,11 +205,7 @@ class RedisJsonCommandBuilder<K, V> extends BaseRedisCommandBuilder<K, V> {
         notNullKey(key);
 
         CommandArgs<K, V> args = new CommandArgs<>(codec).addKey(key);
-
-        if (jsonPath != null && !jsonPath.isRootPath()) {
-            args.add(jsonPath.toString());
-        }
-
+        args.add(jsonPath.toString());
         args.add(number.toString());
 
         return createCommand(JSON_NUMINCRBY, new NumberListOutput<>(codec), args);
@@ -230,6 +217,7 @@ class RedisJsonCommandBuilder<K, V> extends BaseRedisCommandBuilder<K, V> {
         CommandArgs<K, V> args = new CommandArgs<>(codec).addKey(key);
 
         if (jsonPath != null && !jsonPath.isRootPath()) {
+            // OPTIONAL as per API
             args.add(jsonPath.toString());
         }
 
@@ -243,6 +231,7 @@ class RedisJsonCommandBuilder<K, V> extends BaseRedisCommandBuilder<K, V> {
         CommandArgs<K, V> args = new CommandArgs<>(codec).addKey(key);
 
         if (jsonPath != null && !jsonPath.isRootPath()) {
+            // OPTIONAL as per API
             args.add(jsonPath.toString());
         }
 
@@ -259,6 +248,7 @@ class RedisJsonCommandBuilder<K, V> extends BaseRedisCommandBuilder<K, V> {
         args.add(value.asByteBuffer().array());
 
         if (options != null) {
+            // OPTIONAL as per API
             options.build(args);
         }
 
@@ -271,6 +261,7 @@ class RedisJsonCommandBuilder<K, V> extends BaseRedisCommandBuilder<K, V> {
         CommandArgs<K, V> args = new CommandArgs<>(codec).addKey(key);
 
         if (jsonPath != null && !jsonPath.isRootPath()) {
+            // OPTIONAL as per API
             args.add(jsonPath.toString());
         }
 
@@ -286,6 +277,7 @@ class RedisJsonCommandBuilder<K, V> extends BaseRedisCommandBuilder<K, V> {
         CommandArgs<K, V> args = new CommandArgs<>(codec).addKey(key);
 
         if (jsonPath != null && !jsonPath.isRootPath()) {
+            // OPTIONAL as per API
             args.add(jsonPath.toString());
         }
 
@@ -296,10 +288,7 @@ class RedisJsonCommandBuilder<K, V> extends BaseRedisCommandBuilder<K, V> {
         notNullKey(key);
 
         CommandArgs<K, V> args = new CommandArgs<>(codec).addKey(key);
-
-        if (jsonPath != null && !jsonPath.isRootPath()) {
-            args.add(jsonPath.toString());
-        }
+        args.add(jsonPath.toString());
 
         return createCommand(JSON_TOGGLE, (CommandOutput) new ArrayOutput<>(codec), args);
     }
@@ -310,6 +299,7 @@ class RedisJsonCommandBuilder<K, V> extends BaseRedisCommandBuilder<K, V> {
         CommandArgs<K, V> args = new CommandArgs<>(codec).addKey(key);
 
         if (jsonPath != null && !jsonPath.isRootPath()) {
+            // OPTIONAL as per API
             args.add(jsonPath.toString());
         }
         return createCommand(JSON_DEL, new IntegerOutput<>(codec), args);
@@ -321,6 +311,7 @@ class RedisJsonCommandBuilder<K, V> extends BaseRedisCommandBuilder<K, V> {
         CommandArgs<K, V> args = new CommandArgs<>(codec).addKey(key);
 
         if (jsonPath != null && !jsonPath.isRootPath()) {
+            // OPTIONAL as per API
             args.add(jsonPath.toString());
         }
 


Test patch:
diff --git a/src/test/java/io/lettuce/core/RedisJsonCommandBuilderUnitTests.java b/src/test/java/io/lettuce/core/RedisJsonCommandBuilderUnitTests.java
index 22f3aed34..fbb2ae3fb 100644
--- a/src/test/java/io/lettuce/core/RedisJsonCommandBuilderUnitTests.java
+++ b/src/test/java/io/lettuce/core/RedisJsonCommandBuilderUnitTests.java
@@ -57,8 +57,18 @@ class RedisJsonCommandBuilderUnitTests {
         ByteBuf buf = Unpooled.directBuffer();
         command.encode(buf);
 
-        assertThat(buf.toString(StandardCharsets.UTF_8)).isEqualTo("*4\r\n" + "$14\r\n" + "JSON.ARRAPPEND\r\n" + "$15\r\n"
-                + "bikes:inventory\r\n" + "$17\r\n" + "$..commuter_bikes\r\n" + "$14\r\n" + ID_BIKE_6 + "\r\n");
+        assertThat(buf.toString(StandardCharsets.UTF_8)).isEqualTo("*4\r\n" + "$14\r\nJSON.ARRAPPEND\r\n"
+                + "$15\r\nbikes:inventory\r\n" + "$17\r\n$..commuter_bikes\r\n" + "$14\r\n" + ID_BIKE_6 + "\r\n");
+    }
+
+    @Test
+    void shouldCorrectlyConstructJsonArrappendRootPath() {
+        Command<String, String, List<Long>> command = builder.jsonArrappend(MY_KEY, JsonPath.ROOT_PATH, ELEMENT);
+        ByteBuf buf = Unpooled.directBuffer();
+        command.encode(buf);
+
+        assertThat(buf.toString(StandardCharsets.UTF_8)).isEqualTo(
+                "*3\r\n" + "$14\r\nJSON.ARRAPPEND\r\n" + "$15\r\nbikes:inventory\r\n" + "$14\r\n" + ID_BIKE_6 + "\r\n");
     }
 
     @Test
@@ -68,9 +78,21 @@ class RedisJsonCommandBuilderUnitTests {
         ByteBuf buf = Unpooled.directBuffer();
         command.encode(buf);
 
-        assertThat(buf.toString(StandardCharsets.UTF_8)).isEqualTo("*6\r\n" + "$13\r\n" + "JSON.ARRINDEX\r\n" + "$15\r\n"
-                + "bikes:inventory\r\n" + "$17\r\n" + "$..commuter_bikes\r\n" + "$14\r\n" + ID_BIKE_6 + "\r\n" + "$1" + "\r\n"
-                + "0" + "\r\n" + "$1" + "\r\n" + "1" + "\r\n");
+        assertThat(buf.toString(StandardCharsets.UTF_8))
+                .isEqualTo("*6\r\n" + "$13\r\nJSON.ARRINDEX\r\n" + "$15\r\nbikes:inventory\r\n" + "$17\r\n$..commuter_bikes\r\n"
+                        + "$14\r\n" + ID_BIKE_6 + "\r\n" + "$1\r\n0\r\n" + "$1\r\n1\r\n");
+    }
+
+    @Test
+    void shouldCorrectlyConstructJsonArrindexRootPath() {
+        JsonRangeArgs range = JsonRangeArgs.Builder.start(0).stop(1);
+        Command<String, String, List<Long>> command = builder.jsonArrindex(MY_KEY, JsonPath.ROOT_PATH, ELEMENT, range);
+        ByteBuf buf = Unpooled.directBuffer();
+        command.encode(buf);
+
+        assertThat(buf.toString(StandardCharsets.UTF_8))
+                .isEqualTo("*6\r\n" + "$13\r\nJSON.ARRINDEX\r\n" + "$15\r\nbikes:inventory\r\n" + "$1\r\n$\r\n" + "$14\r\n"
+                        + ID_BIKE_6 + "\r\n" + "$1\r\n0\r\n" + "$1\r\n1\r\n");
     }
 
     @Test
@@ -80,8 +102,18 @@ class RedisJsonCommandBuilderUnitTests {
         command.encode(buf);
 
         assertThat(buf.toString(StandardCharsets.UTF_8))
-                .isEqualTo("*5\r\n" + "$14\r\n" + "JSON.ARRINSERT\r\n" + "$15\r\n" + "bikes:inventory\r\n" + "$17\r\n"
-                        + "$..commuter_bikes\r\n" + "$1" + "\r\n" + "1" + "\r\n" + "$14\r\n" + ID_BIKE_6 + "\r\n");
+                .isEqualTo("*5\r\n" + "$14\r\nJSON.ARRINSERT\r\n" + "$15\r\nbikes:inventory\r\n"
+                        + "$17\r\n$..commuter_bikes\r\n" + "$1\r\n1\r\n" + "$14\r\n" + ID_BIKE_6 + "\r\n");
+    }
+
+    @Test
+    void shouldCorrectlyConstructJsonArrinsertRootPath() {
+        Command<String, String, List<Long>> command = builder.jsonArrinsert(MY_KEY, JsonPath.ROOT_PATH, 1, ELEMENT);
+        ByteBuf buf = Unpooled.directBuffer();
+        command.encode(buf);
+
+        assertThat(buf.toString(StandardCharsets.UTF_8)).isEqualTo("*5\r\n" + "$14\r\n" + "JSON.ARRINSERT\r\n" + "$15\r\n"
+                + "bikes:inventory\r\n" + "$1\r\n" + "$\r\n" + "$1" + "\r\n" + "1" + "\r\n" + "$14\r\n" + ID_BIKE_6 + "\r\n");
     }
 
     @Test
@@ -94,6 +126,16 @@ class RedisJsonCommandBuilderUnitTests {
                 + "bikes:inventory\r\n" + "$17\r\n" + "$..commuter_bikes\r\n");
     }
 
+    @Test
+    void shouldCorrectlyConstructJsonArrlenRootPath() {
+        Command<String, String, List<Long>> command = builder.jsonArrlen(MY_KEY, JsonPath.ROOT_PATH);
+        ByteBuf buf = Unpooled.directBuffer();
+        command.encode(buf);
+
+        assertThat(buf.toString(StandardCharsets.UTF_8))
+                .isEqualTo("*2\r\n" + "$11\r\nJSON.ARRLEN\r\n" + "$15\r\nbikes:inventory\r\n");
+    }
+
     @Test
     void shouldCorrectlyConstructJsonArrpop() {
         Command<String, String, List<JsonValue>> command = builder.jsonArrpop(MY_KEY, MY_PATH, 3);
@@ -104,6 +146,26 @@ class RedisJsonCommandBuilderUnitTests {
                 + "bikes:inventory\r\n" + "$17\r\n" + "$..commuter_bikes\r\n" + "$1" + "\r\n" + "3" + "\r\n");
     }
 
+    @Test
+    void shouldCorrectlyConstructJsonArrpopNoIndex() {
+        Command<String, String, List<JsonValue>> command = builder.jsonArrpop(MY_KEY, MY_PATH, -1);
+        ByteBuf buf = Unpooled.directBuffer();
+        command.encode(buf);
+
+        assertThat(buf.toString(StandardCharsets.UTF_8)).isEqualTo("*3\r\n" + "$11\r\n" + "JSON.ARRPOP\r\n" + "$15\r\n"
+                + "bikes:inventory\r\n" + "$17\r\n" + "$..commuter_bikes\r\n");
+    }
+
+    @Test
+    void shouldCorrectlyConstructJsonArrpopRootPath() {
+        Command<String, String, List<JsonValue>> command = builder.jsonArrpop(MY_KEY, JsonPath.ROOT_PATH, -1);
+        ByteBuf buf = Unpooled.directBuffer();
+        command.encode(buf);
+
+        assertThat(buf.toString(StandardCharsets.UTF_8))
+                .isEqualTo("*2\r\n" + "$11\r\n" + "JSON.ARRPOP\r\n" + "$15\r\n" + "bikes:inventory\r\n");
+    }
+
     @Test
     void shouldCorrectlyConstructJsonArrtrim() {
         JsonRangeArgs range = JsonRangeArgs.Builder.start(0).stop(1);
@@ -116,6 +178,17 @@ class RedisJsonCommandBuilderUnitTests {
                         + "$..commuter_bikes\r\n" + "$1" + "\r\n" + "0" + "\r\n" + "$1" + "\r\n" + "1" + "\r\n");
     }
 
+    @Test
+    void shouldCorrectlyConstructJsonArrtrimRootPath() {
+        JsonRangeArgs range = JsonRangeArgs.Builder.start(0).stop(1);
+        Command<String, String, List<Long>> command = builder.jsonArrtrim(MY_KEY, JsonPath.ROOT_PATH, range);
+        ByteBuf buf = Unpooled.directBuffer();
+        command.encode(buf);
+
+        assertThat(buf.toString(StandardCharsets.UTF_8)).isEqualTo("*5\r\n" + "$12\r\n" + "JSON.ARRTRIM\r\n" + "$15\r\n"
+                + "bikes:inventory\r\n" + "$1\r\n" + "$\r\n" + "$1" + "\r\n" + "0" + "\r\n" + "$1" + "\r\n" + "1" + "\r\n");
+    }
+
     @Test
     void shouldCorrectlyConstructJsonClear() {
         Command<String, String, Long> command = builder.jsonClear(MY_KEY, MY_PATH);
@@ -126,6 +199,16 @@ class RedisJsonCommandBuilderUnitTests {
                 + "bikes:inventory\r\n" + "$17\r\n" + "$..commuter_bikes\r\n");
     }
 
+    @Test
+    void shouldCorrectlyConstructJsonClearRootPath() {
+        Command<String, String, Long> command = builder.jsonClear(MY_KEY, JsonPath.ROOT_PATH);
+        ByteBuf buf = Unpooled.directBuffer();
+        command.encode(buf);
+
+        assertThat(buf.toString(StandardCharsets.UTF_8))
+                .isEqualTo("*2\r\n" + "$10\r\n" + "JSON.CLEAR\r\n" + "$15\r\n" + "bikes:inventory\r\n");
+    }
+
     @Test
     void shouldCorrectlyConstructJsonGet() {
         JsonGetArgs args = JsonGetArgs.Builder.indent("   ").newline("\n").space("/");
@@ -138,6 +221,18 @@ class RedisJsonCommandBuilderUnitTests {
                 + "\n\r\n" + "$5\r\n" + "SPACE\r\n" + "$1\r\n" + "/\r\n" + "$17\r\n" + "$..commuter_bikes\r\n");
     }
 
+    @Test
+    void shouldCorrectlyConstructJsonGetRootPath() {
+        JsonGetArgs args = JsonGetArgs.Builder.indent("   ").newline("\n").space("/");
+        Command<String, String, List<JsonValue>> command = builder.jsonGet(MY_KEY, args, JsonPath.ROOT_PATH);
+        ByteBuf buf = Unpooled.directBuffer();
+        command.encode(buf);
+
+        assertThat(buf.toString(StandardCharsets.UTF_8)).isEqualTo("*9\r\n" + "$8\r\n" + "JSON.GET\r\n" + "$15\r\n"
+                + "bikes:inventory\r\n" + "$6\r\n" + "INDENT\r\n" + "$3\r\n" + "   \r\n" + "$7\r\n" + "NEWLINE\r\n" + "$1\r\n"
+                + "\n\r\n" + "$5\r\n" + "SPACE\r\n" + "$1\r\n" + "/\r\n" + "$1\r\n" + "$\r\n");
+    }
+
     @Test
     void shouldCorrectlyConstructJsonMerge() {
         Command<String, String, String> command = builder.jsonMerge(MY_KEY, MY_PATH, ELEMENT);
@@ -148,6 +243,16 @@ class RedisJsonCommandBuilderUnitTests {
                 + "bikes:inventory\r\n" + "$17\r\n" + "$..commuter_bikes\r\n" + "$14\r\n" + ID_BIKE_6 + "\r\n");
     }
 
+    @Test
+    void shouldCorrectlyConstructJsonMergeRootPath() {
+        Command<String, String, String> command = builder.jsonMerge(MY_KEY, JsonPath.ROOT_PATH, ELEMENT);
+        ByteBuf buf = Unpooled.directBuffer();
+        command.encode(buf);
+
+        assertThat(buf.toString(StandardCharsets.UTF_8)).isEqualTo("*4\r\n" + "$10\r\n" + "JSON.MERGE\r\n" + "$15\r\n"
+                + "bikes:inventory\r\n" + "$1\r\n" + "$\r\n" + "$14\r\n" + ID_BIKE_6 + "\r\n");
+    }
+
     @Test
     void shouldCorrectlyConstructJsonMget() {
         Command<String, String, List<JsonValue>> command = builder.jsonMGet(MY_PATH, MY_KEY, MY_KEY2);
@@ -158,6 +263,16 @@ class RedisJsonCommandBuilderUnitTests {
                 + "bikes:inventory\r\n" + "$15\r\n" + "bikes:repairLog\r\n" + "$17\r\n" + "$..commuter_bikes\r\n");
     }
 
+    @Test
+    void shouldCorrectlyConstructJsonMgetRootPath() {
+        Command<String, String, List<JsonValue>> command = builder.jsonMGet(JsonPath.ROOT_PATH, MY_KEY, MY_KEY2);
+        ByteBuf buf = Unpooled.directBuffer();
+        command.encode(buf);
+
+        assertThat(buf.toString(StandardCharsets.UTF_8)).isEqualTo("*4\r\n" + "$9\r\n" + "JSON.MGET\r\n" + "$15\r\n"
+                + "bikes:inventory\r\n" + "$15\r\n" + "bikes:repairLog\r\n" + "$1\r\n" + "$\r\n");
+    }
+
     @Test
     void shouldCorrectlyConstructJsonMset() {
         JsonMsetArgs<String, String> args1 = new JsonMsetArgs<>(MY_KEY, MY_PATH, ELEMENT);
@@ -169,6 +284,17 @@ class RedisJsonCommandBuilderUnitTests {
                 + "bikes:inventory\r\n" + "$17\r\n" + "$..commuter_bikes\r\n" + "$14\r\n" + ID_BIKE_6 + "\r\n");
     }
 
+    @Test
+    void shouldCorrectlyConstructJsonMsetRootPath() {
+        JsonMsetArgs<String, String> args1 = new JsonMsetArgs<>(MY_KEY, JsonPath.ROOT_PATH, ELEMENT);
+        Command<String, String, String> command = builder.jsonMSet(Collections.singletonList(args1));
+        ByteBuf buf = Unpooled.directBuffer();
+        command.encode(buf);
+
+        assertThat(buf.toString(StandardCharsets.UTF_8)).isEqualTo("*4\r\n" + "$9\r\n" + "JSON.MSET\r\n" + "$15\r\n"
+                + "bikes:inventory\r\n" + "$1\r\n" + "$\r\n" + "$14\r\n" + ID_BIKE_6 + "\r\n");
+    }
+
     @Test
     void shouldCorrectlyConstructJsonNumincrby() {
         Command<String, String, List<Number>> command = builder.jsonNumincrby(MY_KEY, MY_PATH, 3);
@@ -179,6 +305,16 @@ class RedisJsonCommandBuilderUnitTests {
                 + "bikes:inventory\r\n" + "$17\r\n" + "$..commuter_bikes\r\n" + "$1" + "\r\n" + "3" + "\r\n");
     }
 
+    @Test
+    void shouldCorrectlyConstructJsonNumincrbyRootPath() {
+        Command<String, String, List<Number>> command = builder.jsonNumincrby(MY_KEY, JsonPath.ROOT_PATH, 3);
+        ByteBuf buf = Unpooled.directBuffer();
+        command.encode(buf);
+
+        assertThat(buf.toString(StandardCharsets.UTF_8)).isEqualTo("*4\r\n" + "$14\r\n" + "JSON.NUMINCRBY\r\n" + "$15\r\n"
+                + "bikes:inventory\r\n" + "$1\r\n" + "$\r\n" + "$1" + "\r\n" + "3" + "\r\n");
+    }
+
     @Test
     void shouldCorrectlyConstructJsonObjkeys() {
         Command<String, String, List<String>> command = builder.jsonObjkeys(MY_KEY, MY_PATH);
@@ -189,6 +325,16 @@ class RedisJsonCommandBuilderUnitTests {
                 + "bikes:inventory\r\n" + "$17\r\n" + "$..commuter_bikes\r\n");
     }
 
+    @Test
+    void shouldCorrectlyConstructJsonObjkeysRootPath() {
+        Command<String, String, List<String>> command = builder.jsonObjkeys(MY_KEY, JsonPath.ROOT_PATH);
+        ByteBuf buf = Unpooled.directBuffer();
+        command.encode(buf);
+
+        assertThat(buf.toString(StandardCharsets.UTF_8))
+                .isEqualTo("*2\r\n" + "$12\r\n" + "JSON.OBJKEYS\r\n" + "$15\r\n" + "bikes:inventory\r\n");
+    }
+
     @Test
     void shouldCorrectlyConstructJsonObjlen() {
         Command<String, String, List<Long>> command = builder.jsonObjlen(MY_KEY, MY_PATH);
@@ -199,6 +345,16 @@ class RedisJsonCommandBuilderUnitTests {
                 + "bikes:inventory\r\n" + "$17\r\n" + "$..commuter_bikes\r\n");
     }
 
+    @Test
+    void shouldCorrectlyConstructJsonObjlenRootPath() {
+        Command<String, String, List<Long>> command = builder.jsonObjlen(MY_KEY, JsonPath.ROOT_PATH);
+        ByteBuf buf = Unpooled.directBuffer();
+        command.encode(buf);
+
+        assertThat(buf.toString(StandardCharsets.UTF_8))
+                .isEqualTo("*2\r\n" + "$11\r\n" + "JSON.OBJLEN\r\n" + "$15\r\n" + "bikes:inventory\r\n");
+    }
+
     @Test
     void shouldCorrectlyConstructJsonSet() {
         JsonSetArgs args = JsonSetArgs.Builder.nx();
@@ -211,6 +367,17 @@ class RedisJsonCommandBuilderUnitTests {
                         + "$..commuter_bikes\r\n" + "$14\r\n" + ID_BIKE_6 + "\r\n" + "$2\r\n" + "NX\r\n");
     }
 
+    @Test
+    void shouldCorrectlyConstructJsonSetRootPath() {
+        JsonSetArgs args = JsonSetArgs.Builder.nx();
+        Command<String, String, String> command = builder.jsonSet(MY_KEY, JsonPath.ROOT_PATH, ELEMENT, args);
+        ByteBuf buf = Unpooled.directBuffer();
+        command.encode(buf);
+
+        assertThat(buf.toString(StandardCharsets.UTF_8)).isEqualTo("*5\r\n" + "$8\r\n" + "JSON.SET\r\n" + "$15\r\n"
+                + "bikes:inventory\r\n" + "$1\r\n" + "$\r\n" + "$14\r\n" + ID_BIKE_6 + "\r\n" + "$2\r\n" + "NX\r\n");
+    }
+
     @Test
     void shouldCorrectlyConstructJsonStrappend() {
         Command<String, String, List<Long>> command = builder.jsonStrappend(MY_KEY, MY_PATH, ELEMENT);
@@ -221,6 +388,16 @@ class RedisJsonCommandBuilderUnitTests {
                 + "bikes:inventory\r\n" + "$17\r\n" + "$..commuter_bikes\r\n" + "$14\r\n" + ID_BIKE_6 + "\r\n");
     }
 
+    @Test
+    void shouldCorrectlyConstructJsonStrappendRootPath() {
+        Command<String, String, List<Long>> command = builder.jsonStrappend(MY_KEY, JsonPath.ROOT_PATH, ELEMENT);
+        ByteBuf buf = Unpooled.directBuffer();
+        command.encode(buf);
+
+        assertThat(buf.toString(StandardCharsets.UTF_8)).isEqualTo("*3\r\n" + "$14\r\n" + "JSON.STRAPPEND\r\n" + "$15\r\n"
+                + "bikes:inventory\r\n" + "$14\r\n" + ID_BIKE_6 + "\r\n");
+    }
+
     @Test
     void shouldCorrectlyConstructJsonStrlen() {
         Command<String, String, List<Long>> command = builder.jsonStrlen(MY_KEY, MY_PATH);
@@ -231,6 +408,16 @@ class RedisJsonCommandBuilderUnitTests {
                 + "bikes:inventory\r\n" + "$17\r\n" + "$..commuter_bikes\r\n");
     }
 
+    @Test
+    void shouldCorrectlyConstructJsonStrlenRootPath() {
+        Command<String, String, List<Long>> command = builder.jsonStrlen(MY_KEY, JsonPath.ROOT_PATH);
+        ByteBuf buf = Unpooled.directBuffer();
+        command.encode(buf);
+
+        assertThat(buf.toString(StandardCharsets.UTF_8))
+                .isEqualTo("*2\r\n" + "$11\r\n" + "JSON.STRLEN\r\n" + "$15\r\n" + "bikes:inventory\r\n");
+    }
+
     @Test
     void shouldCorrectlyConstructJsonToggle() {
         Command<String, String, List<Long>> command = builder.jsonToggle(MY_KEY, MY_PATH);
@@ -241,6 +428,16 @@ class RedisJsonCommandBuilderUnitTests {
                 + "bikes:inventory\r\n" + "$17\r\n" + "$..commuter_bikes\r\n");
     }
 
+    @Test
+    void shouldCorrectlyConstructJsonToggleRootPath() {
+        Command<String, String, List<Long>> command = builder.jsonToggle(MY_KEY, JsonPath.ROOT_PATH);
+        ByteBuf buf = Unpooled.directBuffer();
+        command.encode(buf);
+
+        assertThat(buf.toString(StandardCharsets.UTF_8))
+                .isEqualTo("*3\r\n" + "$11\r\n" + "JSON.TOGGLE\r\n" + "$15\r\n" + "bikes:inventory\r\n" + "$1\r\n" + "$\r\n");
+    }
+
     @Test
     void shouldCorrectlyConstructJsonDel() {
         Command<String, String, Long> command = builder.jsonDel(MY_KEY, MY_PATH);
@@ -251,6 +448,16 @@ class RedisJsonCommandBuilderUnitTests {
                 "*3\r\n" + "$8\r\n" + "JSON.DEL\r\n" + "$15\r\n" + "bikes:inventory\r\n" + "$17\r\n" + "$..commuter_bikes\r\n");
     }
 
+    @Test
+    void shouldCorrectlyConstructJsonDelRootPath() {
+        Command<String, String, Long> command = builder.jsonDel(MY_KEY, JsonPath.ROOT_PATH);
+        ByteBuf buf = Unpooled.directBuffer();
+        command.encode(buf);
+
+        assertThat(buf.toString(StandardCharsets.UTF_8))
+                .isEqualTo("*2\r\n" + "$8\r\n" + "JSON.DEL\r\n" + "$15\r\n" + "bikes:inventory\r\n");
+    }
+
     @Test
     void shouldCorrectlyConstructJsonType() {
         Command<String, String, ?> command = builder.jsonType(MY_KEY, MY_PATH);