Repository: redis/lettuce (java)

Issue:
Some json command assume optional path while it's not
## Bug Report

#### Current Behavior

some command, i.e. json.arrindex return wrong number of arguments when path is $ or .

<details>
<summary>Stack trace</summary>

```java
{"name":"testStarted","attributes":{"name":"org.infinispan.server.resp.JsonCommandsTest#testDefaultPath\\[authz\\\u003dfalse\\]()"}}{"name":"testFailed","attributes":{"name":"org.infinispan.server.resp.JsonCommandsTest#testDefaultPath\\[authz\\\u003dfalse\\]()","message":"ERR wrong number of arguments for \u0027json.arrindex\u0027 command","trace":"io.lettuce.core.RedisCommandExecutionException: ERR wrong number of arguments for \u0027json.arrindex\u0027 command
        at io.lettuce.core.internal.ExceptionFactory.createExecutionException(ExceptionFactory.java:151)
        at io.lettuce.core.internal.Exceptions.bubble(Exceptions.java:57)
        at io.lettuce.core.internal.Futures.awaitOrCancel(Futures.java:237)
        at io.lettuce.core.FutureSyncInvocationHandler.handleInvocation(FutureSyncInvocationHandler.java:79)
        at io.lettuce.core.internal.AbstractInvocationHandler.invoke(AbstractInvocationHandler.java:86)
        at jdk.proxy2/jdk.proxy2.$Proxy38.jsonArrindex(Unknown Source)
        at org.infinispan.server.resp.JsonCommandsTest.testDefaultPath(JsonCommandsTest.java:1426)
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
        at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.base/java.lang.reflect.Method.invoke(Method.java:569)
        at org.testng.internal.MethodInvocationHelper.invokeMethod(MethodInvocationHelper.java:124)
        at org.testng.internal.MethodInvocationHelper$1.runTestMethod(MethodInvocationHelper.java:230)
        at org.infinispan.commons.test.TestNGLongTestsHook.run(TestNGLongTestsHook.java:24)
        at org.testng.internal.MethodInvocationHelper.invokeHookable(MethodInvocationHelper.java:242)
        at org.testng.internal.Invoker.invokeMethod(Invoker.java:579)
        at org.testng.internal.Invoker.invokeTestMethod(Invoker.java:719)
        at org.testng.internal.Invoker.invokeTestMethods(Invoker.java:989)
        at org.testng.internal.TestMethodWorker.invokeTestMethods(TestMethodWorker.java:125)
        at org.testng.internal.TestMethodWorker.run(TestMethodWorker.java:109)
        at org.testng.TestRunner.privateRun(TestRunner.java:648)
        at org.testng.TestRunner.run(TestRunner.java:505)
        at org.testng.SuiteRunner.runTest(SuiteRunner.java:455)
        at org.testng.SuiteRunner.runSequentially(SuiteRunner.java:450)
        at org.testng.SuiteRunner.privateRun(SuiteRunner.java:415)
        at org.testng.SuiteRunner.run(SuiteRunner.java:364)
        at org.testng.SuiteRunnerWorker.runSuite(SuiteRunnerWorker.java:52)
        at org.testng.SuiteRunnerWorker.run(SuiteRunnerWorker.java:84)
        at org.testng.TestNG.runSuitesSequentially(TestNG.java:1208)
        at org.testng.TestNG.runSuitesLocally(TestNG.java:1137)
        at org.testng.TestNG.runSuites(TestNG.java:1049)
        at org.testng.TestNG.run(TestNG.java:1017)
        at com.microsoft.java.test.runner.testng.TestNGRunner.run(TestNGRunner.java:45)
        at com.microsoft.java.test.runner.testng.TestNGLauncher.execute(TestNGLauncher.java:32)
        at com.microsoft.java.test.runner.Launcher.main(Launcher.java:57)
Caused by: io.lettuce.core.RedisCommandExecutionException: ERR wrong number of arguments for \u0027json.arrindex\u0027 command
        at io.lettuce.core.internal.ExceptionFactory.createExecutionException(ExceptionFactory.java:151)
        at io.lettuce.core.internal.ExceptionFactory.createExecutionException(ExceptionFactory.java:120)
        at io.lettuce.core.protocol.AsyncCommand.completeResult(AsyncCommand.java:124)
        at io.lettuce.core.protocol.AsyncCommand.complete(AsyncCommand.java:115)
        at io.lettuce.core.protocol.CommandWrapper.complete(CommandWrapper.java:67)
        at io.lettuce.core.protocol.CommandHandler.complete(CommandHandler.java:762)
        at io.lettuce.core.protocol.CommandHandler.decode(CommandHandler.java:697)
        at io.lettuce.core.protocol.CommandHandler.channelRead(CommandHandler.java:614)
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:442)
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420)
        at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412)
        at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1357)
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:440)
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420)
        at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:868)
        at io.netty.channel.epoll.AbstractEpollStreamChannel$EpollStreamUnsafe.epollInReady(AbstractEpollStreamChannel.java:799)
        at io.netty.channel.epoll.EpollEventLoop.processReady(EpollEventLoop.java:501)
        at io.netty.channel.epoll.EpollEventLoop.run(EpollEventLoop.java:399)
        at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:998)
        at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
        at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
        at java.base/java.lang.Thread.run(Thread.java:840)
```

</details>

#### Input Code

<details>
<summary>Input Code</summary>

```java
      JsonValue jv1 = defaultJsonParser.createJsonValue("[1,2,3]");
      JsonValue jSearch = defaultJsonParser.createJsonValue("1");

      syncCommands.jsonSet("key", new JsonPath("$"), jv1);
      syncCommands.jsonArrindex("key", new JsonPath("$"), jSearch);
```

</details>

#### Expected behavior/code

<!-- A clear and concise description of what you expected to happen (or code). -->

#### Environment

- Lettuce version(s): 6.5.4.RELEASE
- Redis version: 7.4.2

#### Possible Solution

<!-- Only if you have suggestions on a fix for the bug -->

#### Additional context

<!-- Add any other context about the problem here. Do not add code as screenshots. -->


Pull request:
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.
-->
Closes #3198 

Several of the JSON.* commands were treating default path in a wrong way

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);