From 4e97650e22b90490b7dbdadc79d9ac0dc7cc8fb7 Mon Sep 17 00:00:00 2001 From: James Xin Date: Tue, 15 Oct 2024 16:44:41 -0700 Subject: [PATCH 1/7] Java: add JSON.SET and JSON.GET Signed-off-by: James Xin --- .../api/commands/servermodules/GlideJson.java | 411 ++++++++++++++++++ .../models/commands/json/JsonGetOptions.java | 56 +++ java/client/src/main/java/module-info.java | 1 + .../commands/servermodules/GlideJsonTest.java | 346 +++++++++++++++ java/integTest/build.gradle | 14 + .../test/java/glide/modules/JsonTests.java | 150 ++++++- 6 files changed, 975 insertions(+), 3 deletions(-) create mode 100644 java/client/src/main/java/glide/api/commands/servermodules/GlideJson.java create mode 100644 java/client/src/main/java/glide/api/models/commands/json/JsonGetOptions.java create mode 100644 java/client/src/test/java/glide/api/commands/servermodules/GlideJsonTest.java diff --git a/java/client/src/main/java/glide/api/commands/servermodules/GlideJson.java b/java/client/src/main/java/glide/api/commands/servermodules/GlideJson.java new file mode 100644 index 0000000000..48930aba74 --- /dev/null +++ b/java/client/src/main/java/glide/api/commands/servermodules/GlideJson.java @@ -0,0 +1,411 @@ +/** Copyright Valkey GLIDE Project Contributors - SPDX Identifier: Apache-2.0 */ +package glide.api.commands.servermodules; + +import static glide.api.models.GlideString.gs; + +import glide.api.BaseClient; +import glide.api.GlideClient; +import glide.api.GlideClusterClient; +import glide.api.models.ClusterValue; +import glide.api.models.GlideString; +import glide.api.models.commands.ConditionalChange; +import glide.api.models.commands.json.JsonGetOptions; +import glide.utils.ArgsBuilder; +import glide.utils.ArrayTransformUtils; +import java.util.concurrent.CompletableFuture; +import lombok.NonNull; + +public class GlideJson { + + public static final String JSON_PREFIX = "JSON."; + public static final String JSON_SET = JSON_PREFIX + "SET"; + public static final String JSON_GET = JSON_PREFIX + "GET"; + + private GlideJson() {} + + /** + * Sets the JSON value at the specified path stored at key. + * + * @param client The Valkey GLIDE client to execute the command. + * @param key The key of the JSON document. + * @param path Represents the path within the JSON document where the value will be set. The key + * will be modified only if value is added as the last child in the specified + * path, or if the specified path acts as the parent of a new child + * being added. + * @param value The value to set at the specific path, in JSON formatted string. + * @return A simple "OK" response if the value is successfully set. If value isn't + * set because of set_condition, returns null. + * @example + *
{@code
+     * String value = client.Json.set(client, "doc", , ".", "{'a': 1.0, 'b': 2}").get();
+     * assert value.equals("OK");
+     * }
+ */ + public static CompletableFuture set( + @NonNull BaseClient client, + @NonNull String key, + @NonNull String path, + @NonNull String value) { + return executeCommand(client, new String[] {JSON_SET, key, path, value}) + .thenApply(r -> (String) r); + } + + /** + * Sets the JSON value at the specified path stored at key. + * + * @param client The Valkey GLIDE client to execute the command. + * @param key The key of the JSON document. + * @param path Represents the path within the JSON document where the value will be set. The key + * will be modified only if value is added as the last child in the specified + * path, or if the specified path acts as the parent of a new child + * being added. + * @param value The value to set at the specific path, in JSON formatted GlideString. + * @return A simple "OK" response if the value is successfully set. If value isn't + * set because of set_condition, returns null. + * @example + *
{@code
+     * String value = client.Json.set(client, gs("doc"), , gs("."), gs("{'a': 1.0, 'b': 2}")).get();
+     * assert value.equals("OK");
+     * }
+ */ + public static CompletableFuture set( + @NonNull BaseClient client, + @NonNull GlideString key, + @NonNull GlideString path, + @NonNull GlideString value) { + return executeCommand(client, new GlideString[] {gs(JSON_SET), key, path, value}) + .thenApply(r -> (String) r); + } + + /** + * Sets the JSON value at the specified path stored at key. + * + * @param client The Valkey GLIDE client to execute the command. + * @param key The key of the JSON document. + * @param path Represents the path within the JSON document where the value will be set. The key + * will be modified only if value is added as the last child in the specified + * path, or if the specified path acts as the parent of a new child + * being added. + * @param value The value to set at the specific path, in JSON formatted string. + * @param setCondition Set the value only if the given condition is met (within the key or path). + * @return A simple "OK" response if the value is successfully set. If value isn't + * set because of set_condition, returns null. + * @example + *
{@code
+     * String value = client.Json.set(client, "doc", , ".", "{'a': 1.0, 'b': 2}", ConditionalChange.ONLY_IF_DOES_NOT_EXIST).get();
+     * assert value.equals("OK");
+     * }
+ */ + public static CompletableFuture set( + @NonNull BaseClient client, + @NonNull String key, + @NonNull String path, + @NonNull String value, + @NonNull ConditionalChange setCondition) { + return executeCommand( + client, new String[] {JSON_SET, key, path, value, setCondition.getValkeyApi()}) + .thenApply(r -> (String) r); + } + + /** + * Sets the JSON value at the specified path stored at key. + * + * @param client The Valkey GLIDE client to execute the command. + * @param key The key of the JSON document. + * @param path Represents the path within the JSON document where the value will be set. The key + * will be modified only if value is added as the last child in the specified + * path, or if the specified path acts as the parent of a new child + * being added. + * @param value The value to set at the specific path, in JSON formatted GlideString. + * @param setCondition Set the value only if the given condition is met (within the key or path). + * @return A simple "OK" response if the value is successfully set. If value isn't + * set because of set_condition, returns null. + * @example + *
{@code
+     * String value = client.Json.set(client, gs("doc"), , gs("."), gs("{'a': 1.0, 'b': 2}"), ConditionalChange.ONLY_IF_DOES_NOT_EXIST).get();
+     * assert value.equals("OK");
+     * }
+ */ + public static CompletableFuture set( + @NonNull BaseClient client, + @NonNull GlideString key, + @NonNull GlideString path, + @NonNull GlideString value, + @NonNull ConditionalChange setCondition) { + return executeCommand( + client, + new GlideString[] {gs(JSON_SET), key, path, value, gs(setCondition.getValkeyApi())}) + .thenApply(r -> (String) r); + } + + /** + * Retrieves the JSON value at the specified path stored at key. + * + * @param client The Valkey GLIDE client to execute the command. + * @param key The key of the JSON document. + * @param path The path within the JSON document. + * @return For JSONPath (path starts with $): Returns a stringifies JSON list of + * bytes replies for every possible path, or a byte string representation of an empty array, + * if path doesn't exist. For legacy path (path doesn't start with $): Returns a + * byte string representation of the value in path. If path doesn't + * exist, an error is raised. If key doesn't exist, returns null. + * @example + *
{@code
+     * String value = client.Json.set(client, "doc", "$").get();
+     * assert value.equals("{'a': 1.0, 'b': 2}");
+     * }
+ */ + public static CompletableFuture get(BaseClient client, String key, String path) { + return executeCommand(client, new String[] {JSON_GET, key, path}); + } + + /** + * Retrieves the JSON value at the specified path stored at key. + * + * @param client The Valkey GLIDE client to execute the command. + * @param key The key of the JSON document. + * @param path The path within the JSON document. + * @return For JSONPath (path starts with $): Returns a stringifies JSON list of + * bytes replies for every possible path, or a byte string representation of an empty array, + * if path doesn't exist. For legacy path (path doesn't start with $): Returns a + * byte string representation of the value in path. If path doesn't + * exist, an error is raised. If key doesn't exist, returns null. + * @example + *
{@code
+     * GlideString value = client.Json.set(client, gs("doc"), gs("$")).get();
+     * assert value.equals(gs("{'a': 1.0, 'b': 2}"));
+     * }
+ */ + public static CompletableFuture get( + BaseClient client, GlideString key, GlideString path) { + return executeCommand(client, new GlideString[] {gs(JSON_GET), key, path}); + } + + /** + * Retrieves the JSON value at the specified paths stored at key. + * + * @param client The Valkey GLIDE client to execute the command. + * @param key The key of the JSON document. + * @param paths List of paths within the JSON document. + * @return Returns a stringifies JSON object in bytes, in which each path is a key, and it's + * corresponding value, is the value as if the path was executed in the command as a single + * path. If key doesn't exist, returns null. + * @example + *
{@code
+     * String value = client.Json.set(client, "doc", new String[] {"$.a", "$.b"}).get();
+     * assert value.equals("{\"$.a\": [1.0], \"$.b\": [2]}");
+     * }
+ */ + public static CompletableFuture get(BaseClient client, String key, String[] paths) { + return executeCommand( + client, ArrayTransformUtils.concatenateArrays(new String[] {JSON_GET, key}, paths)); + } + + /** + * Retrieves the JSON value at the specified paths stored at key. + * + * @param client The Valkey GLIDE client to execute the command. + * @param key The key of the JSON document. + * @param paths List of paths within the JSON document. + * @return Returns a stringifies JSON object in bytes, in which each path is a key, and it's + * corresponding value, is the value as if the path was executed in the command as a single + * path. If key doesn't exist, returns null. + * @example + *
{@code
+     * GlideString value = client.Json.set(client, gs("doc"), new GlideString[] {gs("$.a"), gs("$.b")}).get();
+     * assert value.equals(gs("{\"$.a\": [1.0], \"$.b\": [2]}"));
+     * }
+ */ + public static CompletableFuture get( + BaseClient client, GlideString key, GlideString[] paths) { + return executeCommand( + client, + ArrayTransformUtils.concatenateArrays(new GlideString[] {gs(JSON_GET), key}, paths)); + } + + /** + * Retrieves the JSON value at the specified path stored at key. + * + * @param client The Valkey GLIDE client to execute the command. + * @param key The key of the JSON document. + * @param path The path within the JSON document. + * @param options Options for formatting the byte representation of the JSON data. See + * JsonGetOptions. + * @return For JSONPath (path starts with $): Returns a stringifies JSON list of + * bytes replies for every possible path, or a byte string representation of an empty array, + * if path doesn't exist. For legacy path (path doesn't start with $): Returns a + * byte string representation of the value in path. If path doesn't + * exist, an error is raised. If key doesn't exist, returns null. + * @example + *
{@code
+     * String value = client.Json.set(client, "doc", "$",
+     *                  JsonGetOptions.builder()
+     *                      .indent("  ")
+     *                      .space(" ")
+     *                      .newline("\n")
+     *                      .build()
+     *                  ).get();
+     * assert value.equals("{\n \"a\": \n  1.0\n ,\n \"b\": \n  2\n }");
+     * }
+ */ + public static CompletableFuture get( + BaseClient client, String key, String path, JsonGetOptions options) { + return executeCommand( + client, + ArrayTransformUtils.concatenateArrays( + new String[] {JSON_GET, key}, options.toArgs(), new String[] {path})); + } + + /** + * Retrieves the JSON value at the specified path stored at key. + * + * @param client The Valkey GLIDE client to execute the command. + * @param key The key of the JSON document. + * @param path The path within the JSON document. + * @param options Options for formatting the byte representation of the JSON data. See + * JsonGetOptions. + * @return For JSONPath (path starts with $): Returns a stringifies JSON list of + * bytes replies for every possible path, or a byte string representation of an empty array, + * if path doesn't exist. For legacy path (path doesn't start with $): Returns a + * byte string representation of the value in path. If path doesn't + * exist, an error is raised. If key doesn't exist, returns null. + * @example + *
{@code
+     * GlideString value = client.Json.set(client, gs("doc"), gs("$"),
+     *                  JsonGetOptions.builder()
+     *                      .indent("  ")
+     *                      .space(" ")
+     *                      .newline("\n")
+     *                      .build()
+     *                  ).get();
+     * assert value.equals(gs("{\n \"a\": \n  1.0\n ,\n \"b\": \n  2\n }"));
+     * }
+ */ + public static CompletableFuture get( + BaseClient client, GlideString key, GlideString path, JsonGetOptions options) { + return executeCommand( + client, + new ArgsBuilder().add(gs(JSON_GET)).add(key).add(options.toArgs()).add(path).toArray()); + } + + /** + * Retrieves the JSON value at the specified path stored at key. + * + * @param client The Valkey GLIDE client to execute the command. + * @param key The key of the JSON document. + * @param paths List of paths within the JSON document. + * @param options Options for formatting the byte representation of the JSON data. See + * JsonGetOptions. + * @return Returns a stringifies JSON object in bytes, in which each path is a key, and it's + * corresponding value, is the value as if the path was executed in the command as a single + * path. If key doesn't exist, returns null. + * @example + *
{@code
+     * String value = client.Json.set(client, "doc", new String[] {"$.a", "$.b"},
+     *                  JsonGetOptions.builder()
+     *                      .indent("  ")
+     *                      .space(" ")
+     *                      .newline("\n")
+     *                      .build()
+     *                  ).get();
+     * assert value.equals("{\n \"$.a\": [\n  1.0\n ],\n \"$.b\": [\n  2\n ]\n}");
+     * }
+ */ + public static CompletableFuture get( + BaseClient client, String key, String[] paths, JsonGetOptions options) { + return executeCommand( + client, + ArrayTransformUtils.concatenateArrays( + new String[] {JSON_GET, key}, options.toArgs(), paths)); + } + + /** + * Retrieves the JSON value at the specified path stored at key. + * + * @param client The Valkey GLIDE client to execute the command. + * @param key The key of the JSON document. + * @param paths List of paths within the JSON document. + * @param options Options for formatting the byte representation of the JSON data. See + * JsonGetOptions. + * @return Returns a stringifies JSON object in bytes, in which each path is a key, and it's + * corresponding value, is the value as if the path was executed in the command as a single + * path. If key doesn't exist, returns null. + * @example + *
{@code
+     * GlideString value = client.Json.set(client, gs("doc"), new GlideString[] {gs("$.a"), gs("$.b")},
+     *                  JsonGetOptions.builder()
+     *                      .indent("  ")
+     *                      .space(" ")
+     *                      .newline("\n")
+     *                      .build()
+     *                  ).get();
+     * assert value.equals(gs("{\n \"$.a\": [\n  1.0\n ],\n \"$.b\": [\n  2\n ]\n}"));
+     * }
+ */ + public static CompletableFuture get( + BaseClient client, GlideString key, GlideString[] paths, JsonGetOptions options) { + return executeCommand( + client, + new ArgsBuilder().add(gs(JSON_GET)).add(key).add(options.toArgs()).add(paths).toArray()); + } + + /** + * A wrapper for custom command API. + * + * @param client The client to execute the command. + * @param args The command line. + */ + private static CompletableFuture executeCommand(BaseClient client, String[] args) { + return executeCommand(client, args, false); + } + + /** + * A wrapper for custom command API. + * + * @param client The client to execute the command. + * @param args The command line. + * @param returnsMap - true if command returns a map + */ + private static CompletableFuture executeCommand( + BaseClient client, String[] args, boolean returnsMap) { + if (client instanceof GlideClient) { + return ((GlideClient) client).customCommand(args); + } else if (client instanceof GlideClusterClient) { + return ((GlideClusterClient) client) + .customCommand(args) + .thenApply(returnsMap ? ClusterValue::getMultiValue : ClusterValue::getSingleValue); + } + throw new IllegalArgumentException( + "Unknown type of client, should be either `GlideClient` or `GlideClusterClient`"); + } + + /** + * A wrapper for custom command API. + * + * @param client The client to execute the command. + * @param args The command line. + */ + private static CompletableFuture executeCommand(BaseClient client, GlideString[] args) { + return executeCommand(client, args, false); + } + + /** + * A wrapper for custom command API. + * + * @param client The client to execute the command. + * @param args The command line. + * @param returnsMap - true if command returns a map + */ + private static CompletableFuture executeCommand( + BaseClient client, GlideString[] args, boolean returnsMap) { + if (client instanceof GlideClient) { + return ((GlideClient) client).customCommand(args); + } else if (client instanceof GlideClusterClient) { + return ((GlideClusterClient) client) + .customCommand(args) + .thenApply(returnsMap ? ClusterValue::getMultiValue : ClusterValue::getSingleValue); + } + throw new IllegalArgumentException( + "Unknown type of client, should be either `GlideClient` or `GlideClusterClient`"); + } +} diff --git a/java/client/src/main/java/glide/api/models/commands/json/JsonGetOptions.java b/java/client/src/main/java/glide/api/models/commands/json/JsonGetOptions.java new file mode 100644 index 0000000000..c7f719a7e7 --- /dev/null +++ b/java/client/src/main/java/glide/api/models/commands/json/JsonGetOptions.java @@ -0,0 +1,56 @@ +/** Copyright Valkey GLIDE Project Contributors - SPDX Identifier: Apache-2.0 */ +package glide.api.models.commands.json; + +import glide.api.commands.servermodules.GlideJson; +import java.util.ArrayList; +import java.util.List; +import lombok.Builder; +import lombok.Getter; + +/** Additional parameters for {@link GlideJson#get} command. */ +@Getter +@Builder +public final class JsonGetOptions { + /** ValKey API string to designate INDENT */ + public static final String INDENT_VALKEY_API = "INDENT"; + + /** ValKey API string to designate NEWLINE */ + public static final String NEWLINE_VALKEY_API = "NEWLINE"; + + /** ValKey API string to designate SPACE */ + public static final String SPACE_VALKEY_API = "SPACE"; + + /** Sets an indentation string for nested levels. Defaults to null. */ + @Builder.Default private String indent = null; + + /** Sets a string that's printed at the end of each line. Defaults to null. */ + @Builder.Default private String newline = null; + + /** Sets a string that's put between a key and a value. Defaults to null. */ + @Builder.Default private String space = null; + + /** + * Converts JsonGetOptions into a String[]. + * + * @return String[] + */ + public String[] toArgs() { + List resultList = new ArrayList<>(); + if (indent != null) { + resultList.add(INDENT_VALKEY_API); + resultList.add(indent); + } + + if (newline != null) { + resultList.add(NEWLINE_VALKEY_API); + resultList.add(newline); + } + + if (space != null) { + resultList.add(SPACE_VALKEY_API); + resultList.add(space); + } + + return resultList.toArray(new String[0]); + } +} diff --git a/java/client/src/main/java/module-info.java b/java/client/src/main/java/module-info.java index 183e6c0410..fc280da076 100644 --- a/java/client/src/main/java/module-info.java +++ b/java/client/src/main/java/module-info.java @@ -10,6 +10,7 @@ exports glide.api.models.commands.scan; exports glide.api.models.commands.stream; exports glide.api.models.commands.FT; + exports glide.api.models.commands.json; exports glide.api.models.configuration; exports glide.api.models.exceptions; exports glide.api.commands.servermodules; diff --git a/java/client/src/test/java/glide/api/commands/servermodules/GlideJsonTest.java b/java/client/src/test/java/glide/api/commands/servermodules/GlideJsonTest.java new file mode 100644 index 0000000000..6357d5ebb2 --- /dev/null +++ b/java/client/src/test/java/glide/api/commands/servermodules/GlideJsonTest.java @@ -0,0 +1,346 @@ +/** Copyright Valkey GLIDE Project Contributors - SPDX Identifier: Apache-2.0 */ +package glide.api.commands.servermodules; + +import static glide.api.models.GlideString.gs; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.RETURNS_DEEP_STUBS; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import glide.api.GlideClient; +import glide.api.models.GlideString; +import glide.api.models.commands.ConditionalChange; +import glide.api.models.commands.json.JsonGetOptions; +import glide.utils.ArgsBuilder; +import java.util.ArrayList; +import java.util.Collections; +import java.util.concurrent.CompletableFuture; +import lombok.SneakyThrows; +import org.apache.commons.lang3.ArrayUtils; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class GlideJsonTest { + + private GlideClient glideClient; + + @BeforeEach + void setUp() { + glideClient = mock(GlideClient.class, RETURNS_DEEP_STUBS); + } + + @Test + @SneakyThrows + void set_returns_success() { + // setup + String key = "testKey"; + String path = "$"; + String jsonValue = "{\"a\": 1.0, \"b\": 2}"; + CompletableFuture expectedResponse = new CompletableFuture<>(); + String expectedResponseValue = "OK"; + expectedResponse.complete(expectedResponseValue); + when(glideClient + .customCommand(eq(new String[] {GlideJson.JSON_SET, key, path, jsonValue})) + .thenApply(any())) + .thenReturn(expectedResponse); + + // exercise + CompletableFuture actualResponse = GlideJson.set(glideClient, key, path, jsonValue); + String actualResponseValue = actualResponse.get(); + + // verify + assertEquals(expectedResponse, actualResponse); + assertEquals(expectedResponseValue, actualResponseValue); + } + + @Test + @SneakyThrows + void set_binary_returns_success() { + // setup + GlideString key = gs("testKey"); + GlideString path = gs("$"); + GlideString jsonValue = gs("{\"a\": 1.0, \"b\": 2}"); + CompletableFuture expectedResponse = new CompletableFuture<>(); + String expectedResponseValue = "OK"; + expectedResponse.complete(expectedResponseValue); + when(glideClient + .customCommand(eq(new GlideString[] {gs(GlideJson.JSON_SET), key, path, jsonValue})) + .thenApply(any())) + .thenReturn(expectedResponse); + + // exercise + CompletableFuture actualResponse = GlideJson.set(glideClient, key, path, jsonValue); + String actualResponseValue = actualResponse.get(); + + // verify + assertEquals(expectedResponse, actualResponse); + assertEquals(expectedResponseValue, actualResponseValue); + } + + @Test + @SneakyThrows + void set_with_condition_returns_success() { + // setup + String key = "testKey"; + String path = "$"; + String jsonValue = "{\"a\": 1.0, \"b\": 2}"; + ConditionalChange setCondition = ConditionalChange.ONLY_IF_DOES_NOT_EXIST; + CompletableFuture expectedResponse = new CompletableFuture<>(); + String expectedResponseValue = "OK"; + expectedResponse.complete(expectedResponseValue); + when(glideClient + .customCommand( + eq( + new String[] { + GlideJson.JSON_SET, key, path, jsonValue, setCondition.getValkeyApi() + })) + .thenApply(any())) + .thenReturn(expectedResponse); + + // exercise + CompletableFuture actualResponse = + GlideJson.set(glideClient, key, path, jsonValue, setCondition); + String actualResponseValue = actualResponse.get(); + + // verify + assertEquals(expectedResponse, actualResponse); + assertEquals(expectedResponseValue, actualResponseValue); + } + + @Test + @SneakyThrows + void set_binary_with_condition_returns_success() { + // setup + GlideString key = gs("testKey"); + GlideString path = gs("$"); + GlideString jsonValue = gs("{\"a\": 1.0, \"b\": 2}"); + ConditionalChange setCondition = ConditionalChange.ONLY_IF_DOES_NOT_EXIST; + CompletableFuture expectedResponse = new CompletableFuture<>(); + String expectedResponseValue = "OK"; + expectedResponse.complete(expectedResponseValue); + when(glideClient + .customCommand( + eq( + new GlideString[] { + gs(GlideJson.JSON_SET), key, path, jsonValue, gs(setCondition.getValkeyApi()) + })) + .thenApply(any())) + .thenReturn(expectedResponse); + + // exercise + CompletableFuture actualResponse = + GlideJson.set(glideClient, key, path, jsonValue, setCondition); + String actualResponseValue = actualResponse.get(); + + // verify + assertEquals(expectedResponse, actualResponse); + assertEquals(expectedResponseValue, actualResponseValue); + } + + @Test + @SneakyThrows + void get_with_single_path_returns_success() { + // setup + String key = "testKey"; + String path = "$"; + CompletableFuture expectedResponse = new CompletableFuture<>(); + String expectedResponseValue = "{\"a\": 1.0, \"b\": 2}"; + expectedResponse.complete(expectedResponseValue); + when(glideClient.customCommand(eq(new String[] {GlideJson.JSON_GET, key, path}))) + .thenReturn(expectedResponse); + + // exercise + CompletableFuture actualResponse = GlideJson.get(glideClient, key, path); + String actualResponseValue = (String) actualResponse.get(); + + // verify + assertEquals(expectedResponse, actualResponse); + assertEquals(expectedResponseValue, actualResponseValue); + } + + @Test + @SneakyThrows + void get_binary_with_single_path_returns_success() { + // setup + GlideString key = gs("testKey"); + GlideString path = gs("$"); + CompletableFuture expectedResponse = new CompletableFuture<>(); + GlideString expectedResponseValue = gs("{\"a\": 1.0, \"b\": 2}"); + expectedResponse.complete(expectedResponseValue); + when(glideClient.customCommand(eq(new GlideString[] {gs(GlideJson.JSON_GET), key, path}))) + .thenReturn(expectedResponse); + + // exercise + CompletableFuture actualResponse = GlideJson.get(glideClient, key, path); + GlideString actualResponseValue = (GlideString) actualResponse.get(); + + // verify + assertEquals(expectedResponse, actualResponse); + assertEquals(expectedResponseValue, actualResponseValue); + } + + @Test + @SneakyThrows + void get_with_multiple_paths_returns_success() { + // setup + String key = "testKey"; + String path1 = ".firstName"; + String path2 = ".lastName"; + String[] paths = new String[] {path1, path2}; + CompletableFuture expectedResponse = new CompletableFuture<>(); + String expectedResponseValue = "{\"a\": 1.0, \"b\": 2}"; + expectedResponse.complete(expectedResponseValue); + when(glideClient.customCommand(eq(new String[] {GlideJson.JSON_GET, key, path1, path2}))) + .thenReturn(expectedResponse); + + // exercise + CompletableFuture actualResponse = GlideJson.get(glideClient, key, paths); + String actualResponseValue = (String) actualResponse.get(); + + // verify + assertEquals(expectedResponse, actualResponse); + assertEquals(expectedResponseValue, actualResponseValue); + } + + @Test + @SneakyThrows + void get_binary_with_multiple_paths_returns_success() { + // setup + GlideString key = gs("testKey"); + GlideString path1 = gs(".firstName"); + GlideString path2 = gs(".lastName"); + GlideString[] paths = new GlideString[] {path1, path2}; + CompletableFuture expectedResponse = new CompletableFuture<>(); + GlideString expectedResponseValue = gs("{\"a\": 1.0, \"b\": 2}"); + expectedResponse.complete(expectedResponseValue); + when(glideClient.customCommand( + eq(new GlideString[] {gs(GlideJson.JSON_GET), key, path1, path2}))) + .thenReturn(expectedResponse); + + // exercise + CompletableFuture actualResponse = GlideJson.get(glideClient, key, paths); + GlideString actualResponseValue = (GlideString) actualResponse.get(); + + // verify + assertEquals(expectedResponse, actualResponse); + assertEquals(expectedResponseValue, actualResponseValue); + } + + @Test + @SneakyThrows + void get_with_single_path_and_options_returns_success() { + // setup + String key = "testKey"; + String path = "$"; + JsonGetOptions options = JsonGetOptions.builder().indent("\t").space(" ").newline("\n").build(); + CompletableFuture expectedResponse = new CompletableFuture<>(); + String expectedResponseValue = "{\"a\": 1.0, \"b\": 2}"; + expectedResponse.complete(expectedResponseValue); + when(glideClient.customCommand( + eq( + ArrayUtils.add( + ArrayUtils.addAll(new String[] {GlideJson.JSON_GET, key}, options.toArgs()), + path)))) + .thenReturn(expectedResponse); + + // exercise + CompletableFuture actualResponse = GlideJson.get(glideClient, key, path, options); + String actualResponseValue = (String) actualResponse.get(); + + // verify + assertEquals(expectedResponse, actualResponse); + assertEquals(expectedResponseValue, actualResponseValue); + } + + @Test + @SneakyThrows + void get_binary_with_single_path_and_options_returns_success() { + // setup + GlideString key = gs("testKey"); + GlideString path = gs("$"); + JsonGetOptions options = JsonGetOptions.builder().indent("\t").space(" ").newline("\n").build(); + CompletableFuture expectedResponse = new CompletableFuture<>(); + GlideString expectedResponseValue = gs("{\"a\": 1.0, \"b\": 2}"); + expectedResponse.complete(expectedResponseValue); + when(glideClient.customCommand( + eq( + new ArgsBuilder() + .add(new GlideString[] {gs(GlideJson.JSON_GET), key}) + .add(options.toArgs()) + .add(path) + .toArray()))) + .thenReturn(expectedResponse); + + // exercise + CompletableFuture actualResponse = GlideJson.get(glideClient, key, path, options); + GlideString actualResponseValue = (GlideString) actualResponse.get(); + + // verify + assertEquals(expectedResponse, actualResponse); + assertEquals(expectedResponseValue, actualResponseValue); + } + + @Test + @SneakyThrows + void get_with_multiple_paths_and_options_returns_success() { + // setup + String key = "testKey"; + String path1 = ".firstName"; + String path2 = ".lastName"; + JsonGetOptions options = JsonGetOptions.builder().indent("\t").newline("\n").space(" ").build(); + String[] paths = new String[] {path1, path2}; + CompletableFuture expectedResponse = new CompletableFuture<>(); + String expectedResponseValue = "{\"a\": 1.0, \"b\": 2}"; + expectedResponse.complete(expectedResponseValue); + ArrayList argsList = new ArrayList<>(); + argsList.add(GlideJson.JSON_GET); + argsList.add(key); + Collections.addAll(argsList, options.toArgs()); + Collections.addAll(argsList, paths); + when(glideClient.customCommand(eq(argsList.toArray(new String[0])))) + .thenReturn(expectedResponse); + + // exercise + CompletableFuture actualResponse = GlideJson.get(glideClient, key, paths, options); + String actualResponseValue = (String) actualResponse.get(); + + // verify + assertArrayEquals( + new String[] {"INDENT", "\t", "NEWLINE", "\n", "SPACE", " "}, options.toArgs()); + assertEquals(expectedResponse, actualResponse); + assertEquals(expectedResponseValue, actualResponseValue); + } + + @Test + @SneakyThrows + void get_binary_with_multiple_paths_and_options_returns_success() { + // setup + GlideString key = gs("testKey"); + GlideString path1 = gs(".firstName"); + GlideString path2 = gs(".lastName"); + JsonGetOptions options = JsonGetOptions.builder().indent("\t").newline("\n").space(" ").build(); + GlideString[] paths = new GlideString[] {path1, path2}; + CompletableFuture expectedResponse = new CompletableFuture<>(); + GlideString expectedResponseValue = gs("{\"a\": 1.0, \"b\": 2}"); + expectedResponse.complete(expectedResponseValue); + GlideString[] args = + new ArgsBuilder() + .add(GlideJson.JSON_GET) + .add(key) + .add(options.toArgs()) + .add(paths) + .toArray(); + when(glideClient.customCommand(eq(args))).thenReturn(expectedResponse); + + // exercise + CompletableFuture actualResponse = GlideJson.get(glideClient, key, paths, options); + GlideString actualResponseValue = (GlideString) actualResponse.get(); + + // verify + assertEquals(expectedResponse, actualResponse); + assertEquals(expectedResponseValue, actualResponseValue); + } +} diff --git a/java/integTest/build.gradle b/java/integTest/build.gradle index c2032d05d1..9b8048a3bb 100644 --- a/java/integTest/build.gradle +++ b/java/integTest/build.gradle @@ -28,6 +28,9 @@ dependencies { //lombok testCompileOnly 'org.projectlombok:lombok:1.18.32' testAnnotationProcessor 'org.projectlombok:lombok:1.18.32' + + // jsonassert + testImplementation 'org.skyscreamer:jsonassert:1.5.3' } def standaloneHosts = '' @@ -145,3 +148,14 @@ tasks.register('modulesTest', Test) { includeTestsMatching 'glide.modules.*' } } + + +tasks.register('standaloneModulesTest', Test) { + doFirst { + standaloneHosts = System.getProperty("standalone-endpoints") + } + + filter { + includeTestsMatching 'glide.modules.*' + } +} diff --git a/java/integTest/src/test/java/glide/modules/JsonTests.java b/java/integTest/src/test/java/glide/modules/JsonTests.java index 5d6880ae2e..31000d2617 100644 --- a/java/integTest/src/test/java/glide/modules/JsonTests.java +++ b/java/integTest/src/test/java/glide/modules/JsonTests.java @@ -2,22 +2,166 @@ package glide.modules; import static glide.TestUtilities.commonClusterClientConfig; +import static glide.api.BaseClient.OK; +import static glide.api.models.GlideString.gs; +import static glide.api.models.configuration.RequestRoutingConfiguration.SimpleMultiNodeRoute.ALL_PRIMARIES; import static glide.api.models.configuration.RequestRoutingConfiguration.SimpleSingleNodeRoute.RANDOM; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; import glide.api.GlideClusterClient; +import glide.api.commands.servermodules.GlideJson; +import glide.api.models.GlideString; +import glide.api.models.commands.ConditionalChange; +import glide.api.models.commands.FlushMode; import glide.api.models.commands.InfoOptions.Section; +import glide.api.models.commands.json.JsonGetOptions; +import java.util.UUID; import lombok.SneakyThrows; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; +import org.skyscreamer.jsonassert.JSONAssert; +import org.skyscreamer.jsonassert.JSONCompareMode; public class JsonTests { - @Test + + private static GlideClusterClient client; + + @BeforeAll @SneakyThrows - public void check_module_loaded() { - var client = + public static void init() { + client = GlideClusterClient.createClient(commonClusterClientConfig().requestTimeout(5000).build()) .get(); + client.flushall(FlushMode.SYNC, ALL_PRIMARIES).get(); + } + + @AfterAll + @SneakyThrows + public static void teardown() { + client.close(); + } + + @Test + @SneakyThrows + public void check_module_loaded() { var info = client.info(new Section[] {Section.MODULES}, RANDOM).get().getSingleValue(); assertTrue(info.contains("# json_core_metrics")); } + + @Test + @SneakyThrows + public void json_set_get() { + String key = UUID.randomUUID().toString(); + String jsonValue = "{\"a\": 1.0,\"b\": 2}"; + + assertEquals(OK, GlideJson.set(client, key, "$", jsonValue).get()); + + Object getResult = GlideJson.get(client, key, ".").get(); + + assertInstanceOf(String.class, getResult); + JSONAssert.assertEquals(jsonValue, (String) getResult, JSONCompareMode.LENIENT); + + Object getResultWithMultiPaths = GlideJson.get(client, key, new String[] {"$.a", "$.b"}).get(); + + assertInstanceOf(String.class, getResultWithMultiPaths); + JSONAssert.assertEquals( + "{\"$.a\":[1.0],\"$.b\":[2]}", (String) getResultWithMultiPaths, JSONCompareMode.LENIENT); + + assertNull(GlideJson.get(client, "non_existing_key", "$").get()); + assertEquals("[]", GlideJson.get(client, key, "$.d").get()); + } + + @Test + @SneakyThrows + public void json_set_get_multiple_values() { + String key = UUID.randomUUID().toString(); + String jsonValue = "{\"a\": {\"c\": 1, \"d\": 4}, \"b\": {\"c\": 2}, \"c\": true}"; + + assertEquals(OK, GlideJson.set(client, gs(key), gs("$"), gs(jsonValue)).get()); + + Object getResult = GlideJson.get(client, gs(key), gs("$..c")).get(); + + assertInstanceOf(GlideString.class, getResult); + JSONAssert.assertEquals("[true, 1, 2]", ((GlideString)getResult).getString(), JSONCompareMode.LENIENT); + + Object getResultWithMultiPaths = GlideJson.get(client, key, new String[] {"$..c", "$.c"}).get(); + + assertInstanceOf(String.class, getResultWithMultiPaths); + JSONAssert.assertEquals( + "{\"$..c\": [True, 1, 2], \"$.c\": [True]}", + (String) getResultWithMultiPaths, + JSONCompareMode.LENIENT); + + assertEquals(OK, GlideJson.set(client, key, "$..c", "\"new_value\"").get()); + Object getResultAfterSetNewValue = GlideJson.get(client, key, "$..c").get(); + assertInstanceOf(String.class, getResultAfterSetNewValue); + JSONAssert.assertEquals( + "[\"new_value\", \"new_value\", \"new_value\"]", + (String) getResultAfterSetNewValue, + JSONCompareMode.LENIENT); + } + + @Test + @SneakyThrows + public void json_set_get_conditional_set() { + String key = UUID.randomUUID().toString(); + String jsonValue = "{\"a\": 1.0, \"b\": 2}"; + + assertNull(GlideJson.set(client, key, "$", jsonValue, ConditionalChange.ONLY_IF_EXISTS).get()); + assertEquals( + OK, + GlideJson.set(client, key, "$", jsonValue, ConditionalChange.ONLY_IF_DOES_NOT_EXIST).get()); + assertNull( + GlideJson.set(client, key, "$.a", "4.5", ConditionalChange.ONLY_IF_DOES_NOT_EXIST).get()); + assertEquals("1.0", (String) GlideJson.get(client, key, ".a").get()); + assertEquals( + OK, GlideJson.set(client, key, "$.a", "4.5", ConditionalChange.ONLY_IF_EXISTS).get()); + assertEquals("4.5", GlideJson.get(client, key, ".a").get()); + } + + @Test + @SneakyThrows + public void json_set_get_formatting() { + String key = UUID.randomUUID().toString(); + + assertEquals( + OK, + GlideJson.set(client, key, "$", "{\"a\": 1.0, \"b\": 2, \"c\": {\"d\": 3, \"e\": 4}}") + .get()); + + String expectedGetResult = + "[\n" + + " {\n" + + " \"a\": 1.0,\n" + + " \"b\": 2,\n" + + " \"c\": {\n" + + " \"d\": 3,\n" + + " \"e\": 4\n" + + " }\n" + + " }\n" + + "]"; + Object actualGetResult = + GlideJson.get( + client, + key, + "$", + JsonGetOptions.builder().indent(" ").newline("\n").space(" ").build()) + .get(); + assertEquals(expectedGetResult, actualGetResult); + + String expectedGetResult2 = + "[\n~{\n~~\"a\":*1.0,\n~~\"b\":*2,\n~~\"c\":*{\n~~~\"d\":*3,\n~~~\"e\":*4\n~~}\n~}\n]"; + Object actualGetResult2 = + GlideJson.get( + client, + key, + "$", + JsonGetOptions.builder().indent("~").newline("\n").space("*").build()) + .get(); + assertEquals(expectedGetResult2, actualGetResult2); + } } From afc0372d6612555ce23fb595aee9ba96294eb20e Mon Sep 17 00:00:00 2001 From: James Xin Date: Wed, 16 Oct 2024 02:01:39 -0700 Subject: [PATCH 2/7] linter fix Signed-off-by: James Xin --- java/integTest/src/test/java/glide/modules/JsonTests.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/java/integTest/src/test/java/glide/modules/JsonTests.java b/java/integTest/src/test/java/glide/modules/JsonTests.java index 31000d2617..c5eb87e760 100644 --- a/java/integTest/src/test/java/glide/modules/JsonTests.java +++ b/java/integTest/src/test/java/glide/modules/JsonTests.java @@ -86,7 +86,8 @@ public void json_set_get_multiple_values() { Object getResult = GlideJson.get(client, gs(key), gs("$..c")).get(); assertInstanceOf(GlideString.class, getResult); - JSONAssert.assertEquals("[true, 1, 2]", ((GlideString)getResult).getString(), JSONCompareMode.LENIENT); + JSONAssert.assertEquals( + "[true, 1, 2]", ((GlideString) getResult).getString(), JSONCompareMode.LENIENT); Object getResultWithMultiPaths = GlideJson.get(client, key, new String[] {"$..c", "$.c"}).get(); From bccc5f1af6a895de9a5094af014341691c0319ac Mon Sep 17 00:00:00 2001 From: James Xin Date: Wed, 16 Oct 2024 02:06:21 -0700 Subject: [PATCH 3/7] CHANGELOG Signed-off-by: James Xin --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e28f1941ce..a90f628944 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ * Java: Added `FT.CREATE` ([#2414](https://github.com/valkey-io/valkey-glide/pull/2414)) * Java: Added `FT.DROPINDEX` ([#2440](https://github.com/valkey-io/valkey-glide/pull/2440)) * Java: Added `FT.SEARCH` ([#2439](https://github.com/valkey-io/valkey-glide/pull/2439)) +* Java: Added `JSON.SET` and `JSON.GET` ([#2462](https://github.com/valkey-io/valkey-glide/pull/2462)) * Core: Update routing for commands from server modules ([#2461](https://github.com/valkey-io/valkey-glide/pull/2461)) #### Breaking Changes From abe29deb63a68b3f19c164972908f3ae8a182eb3 Mon Sep 17 00:00:00 2001 From: James Xin Date: Wed, 16 Oct 2024 10:03:30 -0700 Subject: [PATCH 4/7] remove temp gradle task Signed-off-by: James Xin --- java/integTest/build.gradle | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/java/integTest/build.gradle b/java/integTest/build.gradle index 9b8048a3bb..70fdf18915 100644 --- a/java/integTest/build.gradle +++ b/java/integTest/build.gradle @@ -148,14 +148,3 @@ tasks.register('modulesTest', Test) { includeTestsMatching 'glide.modules.*' } } - - -tasks.register('standaloneModulesTest', Test) { - doFirst { - standaloneHosts = System.getProperty("standalone-endpoints") - } - - filter { - includeTestsMatching 'glide.modules.*' - } -} From 7c21dc0bd4192e12bed4bd99c4df0f5755c8fe45 Mon Sep 17 00:00:00 2001 From: James Xin Date: Thu, 17 Oct 2024 02:59:57 -0700 Subject: [PATCH 5/7] address comments Signed-off-by: James Xin --- .../{GlideJson.java => Json.java} | 153 +++++++++--------- .../models/commands/json/JsonGetOptions.java | 44 ++--- .../{GlideJsonTest.java => JsonTest.java} | 128 ++++++++------- .../test/java/glide/modules/JsonTests.java | 65 ++++---- 4 files changed, 200 insertions(+), 190 deletions(-) rename java/client/src/main/java/glide/api/commands/servermodules/{GlideJson.java => Json.java} (78%) rename java/client/src/test/java/glide/api/commands/servermodules/{GlideJsonTest.java => JsonTest.java} (70%) diff --git a/java/client/src/main/java/glide/api/commands/servermodules/GlideJson.java b/java/client/src/main/java/glide/api/commands/servermodules/Json.java similarity index 78% rename from java/client/src/main/java/glide/api/commands/servermodules/GlideJson.java rename to java/client/src/main/java/glide/api/commands/servermodules/Json.java index 48930aba74..053c1e6695 100644 --- a/java/client/src/main/java/glide/api/commands/servermodules/GlideJson.java +++ b/java/client/src/main/java/glide/api/commands/servermodules/Json.java @@ -15,13 +15,14 @@ import java.util.concurrent.CompletableFuture; import lombok.NonNull; -public class GlideJson { +/** Module for JSON commands. */ +public class Json { public static final String JSON_PREFIX = "JSON."; public static final String JSON_SET = JSON_PREFIX + "SET"; public static final String JSON_GET = JSON_PREFIX + "GET"; - private GlideJson() {} + private Json() {} /** * Sets the JSON value at the specified path stored at key. @@ -33,11 +34,10 @@ private GlideJson() {} * path, or if the specified path acts as the parent of a new child * being added. * @param value The value to set at the specific path, in JSON formatted string. - * @return A simple "OK" response if the value is successfully set. If value isn't - * set because of set_condition, returns null. + * @return A simple "OK" response if the value is successfully set. * @example *
{@code
-     * String value = client.Json.set(client, "doc", , ".", "{'a': 1.0, 'b': 2}").get();
+     * String value = Json.set(client, "doc", , ".", "{'a': 1.0, 'b': 2}").get();
      * assert value.equals("OK");
      * }
*/ @@ -46,8 +46,7 @@ public static CompletableFuture set( @NonNull String key, @NonNull String path, @NonNull String value) { - return executeCommand(client, new String[] {JSON_SET, key, path, value}) - .thenApply(r -> (String) r); + return executeCommand(client, new String[] {JSON_SET, key, path, value}); } /** @@ -60,8 +59,7 @@ public static CompletableFuture set( * path, or if the specified path acts as the parent of a new child * being added. * @param value The value to set at the specific path, in JSON formatted GlideString. - * @return A simple "OK" response if the value is successfully set. If value isn't - * set because of set_condition, returns null. + * @return A simple "OK" response if the value is successfully set. * @example *
{@code
      * String value = client.Json.set(client, gs("doc"), , gs("."), gs("{'a': 1.0, 'b': 2}")).get();
@@ -73,8 +71,7 @@ public static CompletableFuture set(
             @NonNull GlideString key,
             @NonNull GlideString path,
             @NonNull GlideString value) {
-        return executeCommand(client, new GlideString[] {gs(JSON_SET), key, path, value})
-                .thenApply(r -> (String) r);
+        return executeCommand(client, new GlideString[] {gs(JSON_SET), key, path, value});
     }
 
     /**
@@ -89,7 +86,7 @@ public static CompletableFuture set(
      * @param value The value to set at the specific path, in JSON formatted string.
      * @param setCondition Set the value only if the given condition is met (within the key or path).
      * @return A simple "OK" response if the value is successfully set. If value isn't
-     *     set because of set_condition, returns null.
+     *     set because of setCondition, returns null.
      * @example
      *     
{@code
      * String value = client.Json.set(client, "doc", , ".", "{'a': 1.0, 'b': 2}", ConditionalChange.ONLY_IF_DOES_NOT_EXIST).get();
@@ -103,8 +100,7 @@ public static CompletableFuture set(
             @NonNull String value,
             @NonNull ConditionalChange setCondition) {
         return executeCommand(
-                        client, new String[] {JSON_SET, key, path, value, setCondition.getValkeyApi()})
-                .thenApply(r -> (String) r);
+                client, new String[] {JSON_SET, key, path, value, setCondition.getValkeyApi()});
     }
 
     /**
@@ -119,7 +115,7 @@ public static CompletableFuture set(
      * @param value The value to set at the specific path, in JSON formatted GlideString.
      * @param setCondition Set the value only if the given condition is met (within the key or path).
      * @return A simple "OK" response if the value is successfully set. If value isn't
-     *     set because of set_condition, returns null.
+     *     set because of setCondition, returns null.
      * @example
      *     
{@code
      * String value = client.Json.set(client, gs("doc"), , gs("."), gs("{'a': 1.0, 'b': 2}"), ConditionalChange.ONLY_IF_DOES_NOT_EXIST).get();
@@ -133,9 +129,8 @@ public static CompletableFuture set(
             @NonNull GlideString value,
             @NonNull ConditionalChange setCondition) {
         return executeCommand(
-                        client,
-                        new GlideString[] {gs(JSON_SET), key, path, value, gs(setCondition.getValkeyApi())})
-                .thenApply(r -> (String) r);
+                client,
+                new GlideString[] {gs(JSON_SET), key, path, value, gs(setCondition.getValkeyApi())});
     }
 
     /**
@@ -151,11 +146,12 @@ public static CompletableFuture set(
      *     exist, an error is raised. If key doesn't exist, returns null.
      * @example
      *     
{@code
-     * String value = client.Json.set(client, "doc", "$").get();
+     * String value = client.Json.get(client, "doc", "$").get();
      * assert value.equals("{'a': 1.0, 'b': 2}");
      * }
*/ - public static CompletableFuture get(BaseClient client, String key, String path) { + public static CompletableFuture get( + @NonNull BaseClient client, @NonNull String key, @NonNull String path) { return executeCommand(client, new String[] {JSON_GET, key, path}); } @@ -172,12 +168,12 @@ public static CompletableFuture get(BaseClient client, String key, Strin * exist, an error is raised. If key doesn't exist, returns null. * @example *
{@code
-     * GlideString value = client.Json.set(client, gs("doc"), gs("$")).get();
+     * GlideString value = client.Json.get(client, gs("doc"), gs("$")).get();
      * assert value.equals(gs("{'a': 1.0, 'b': 2}"));
      * }
*/ - public static CompletableFuture get( - BaseClient client, GlideString key, GlideString path) { + public static CompletableFuture get( + @NonNull BaseClient client, @NonNull GlideString key, @NonNull GlideString path) { return executeCommand(client, new GlideString[] {gs(JSON_GET), key, path}); } @@ -192,11 +188,12 @@ public static CompletableFuture get( * path. If key doesn't exist, returns null. * @example *
{@code
-     * String value = client.Json.set(client, "doc", new String[] {"$.a", "$.b"}).get();
+     * String value = client.Json.get(client, "doc", new String[] {"$.a", "$.b"}).get();
      * assert value.equals("{\"$.a\": [1.0], \"$.b\": [2]}");
      * }
*/ - public static CompletableFuture get(BaseClient client, String key, String[] paths) { + public static CompletableFuture get( + @NonNull BaseClient client, @NonNull String key, @NonNull String[] paths) { return executeCommand( client, ArrayTransformUtils.concatenateArrays(new String[] {JSON_GET, key}, paths)); } @@ -212,12 +209,12 @@ public static CompletableFuture get(BaseClient client, String key, Strin * path. If key doesn't exist, returns null. * @example *
{@code
-     * GlideString value = client.Json.set(client, gs("doc"), new GlideString[] {gs("$.a"), gs("$.b")}).get();
+     * GlideString value = client.Json.get(client, gs("doc"), new GlideString[] {gs("$.a"), gs("$.b")}).get();
      * assert value.equals(gs("{\"$.a\": [1.0], \"$.b\": [2]}"));
      * }
*/ - public static CompletableFuture get( - BaseClient client, GlideString key, GlideString[] paths) { + public static CompletableFuture get( + @NonNull BaseClient client, @NonNull GlideString key, @NonNull GlideString[] paths) { return executeCommand( client, ArrayTransformUtils.concatenateArrays(new GlideString[] {gs(JSON_GET), key}, paths)); @@ -238,18 +235,20 @@ public static CompletableFuture get( * exist, an error is raised. If key doesn't exist, returns null. * @example *
{@code
-     * String value = client.Json.set(client, "doc", "$",
-     *                  JsonGetOptions.builder()
-     *                      .indent("  ")
-     *                      .space(" ")
-     *                      .newline("\n")
-     *                      .build()
-     *                  ).get();
+     * JsonGetOptions options = JsonGetOptions.builder()
+     *                              .indent("  ")
+     *                              .space(" ")
+     *                              .newline("\n")
+     *                              .build();
+     * String value = client.Json.get(client, "doc", "$", options).get();
      * assert value.equals("{\n \"a\": \n  1.0\n ,\n \"b\": \n  2\n }");
      * }
*/ - public static CompletableFuture get( - BaseClient client, String key, String path, JsonGetOptions options) { + public static CompletableFuture get( + @NonNull BaseClient client, + @NonNull String key, + @NonNull String path, + @NonNull JsonGetOptions options) { return executeCommand( client, ArrayTransformUtils.concatenateArrays( @@ -271,18 +270,20 @@ public static CompletableFuture get( * exist, an error is raised. If key doesn't exist, returns null. * @example *
{@code
-     * GlideString value = client.Json.set(client, gs("doc"), gs("$"),
-     *                  JsonGetOptions.builder()
-     *                      .indent("  ")
-     *                      .space(" ")
-     *                      .newline("\n")
-     *                      .build()
-     *                  ).get();
+     * JsonGetOptions options = JsonGetOptions.builder()
+     *                              .indent("  ")
+     *                              .space(" ")
+     *                              .newline("\n")
+     *                              .build();
+     * GlideString value = client.Json.get(client, gs("doc"), gs("$"), options).get();
      * assert value.equals(gs("{\n \"a\": \n  1.0\n ,\n \"b\": \n  2\n }"));
      * }
*/ - public static CompletableFuture get( - BaseClient client, GlideString key, GlideString path, JsonGetOptions options) { + public static CompletableFuture get( + @NonNull BaseClient client, + @NonNull GlideString key, + @NonNull GlideString path, + @NonNull JsonGetOptions options) { return executeCommand( client, new ArgsBuilder().add(gs(JSON_GET)).add(key).add(options.toArgs()).add(path).toArray()); @@ -301,18 +302,20 @@ public static CompletableFuture get( * path. If key doesn't exist, returns null. * @example *
{@code
-     * String value = client.Json.set(client, "doc", new String[] {"$.a", "$.b"},
-     *                  JsonGetOptions.builder()
-     *                      .indent("  ")
-     *                      .space(" ")
-     *                      .newline("\n")
-     *                      .build()
-     *                  ).get();
+     * JsonGetOptions options = JsonGetOptions.builder()
+     *                              .indent("  ")
+     *                              .space(" ")
+     *                              .newline("\n")
+     *                              .build();
+     * String value = client.Json.get(client, "doc", new String[] {"$.a", "$.b"}, options).get();
      * assert value.equals("{\n \"$.a\": [\n  1.0\n ],\n \"$.b\": [\n  2\n ]\n}");
      * }
*/ - public static CompletableFuture get( - BaseClient client, String key, String[] paths, JsonGetOptions options) { + public static CompletableFuture get( + @NonNull BaseClient client, + @NonNull String key, + @NonNull String[] paths, + @NonNull JsonGetOptions options) { return executeCommand( client, ArrayTransformUtils.concatenateArrays( @@ -332,18 +335,20 @@ public static CompletableFuture get( * path. If key doesn't exist, returns null. * @example *
{@code
-     * GlideString value = client.Json.set(client, gs("doc"), new GlideString[] {gs("$.a"), gs("$.b")},
-     *                  JsonGetOptions.builder()
-     *                      .indent("  ")
-     *                      .space(" ")
-     *                      .newline("\n")
-     *                      .build()
-     *                  ).get();
+     * JsonGetOptions options = JsonGetOptions.builder()
+     *                              .indent("  ")
+     *                              .space(" ")
+     *                              .newline("\n")
+     *                              .build();
+     * GlideString value = client.Json.get(client, gs("doc"), new GlideString[] {gs("$.a"), gs("$.b")}, options).get();
      * assert value.equals(gs("{\n \"$.a\": [\n  1.0\n ],\n \"$.b\": [\n  2\n ]\n}"));
      * }
*/ - public static CompletableFuture get( - BaseClient client, GlideString key, GlideString[] paths, JsonGetOptions options) { + public static CompletableFuture get( + @NonNull BaseClient client, + @NonNull GlideString key, + @NonNull GlideString[] paths, + @NonNull JsonGetOptions options) { return executeCommand( client, new ArgsBuilder().add(gs(JSON_GET)).add(key).add(options.toArgs()).add(paths).toArray()); @@ -355,7 +360,7 @@ public static CompletableFuture get( * @param client The client to execute the command. * @param args The command line. */ - private static CompletableFuture executeCommand(BaseClient client, String[] args) { + private static CompletableFuture executeCommand(BaseClient client, String[] args) { return executeCommand(client, args, false); } @@ -366,14 +371,16 @@ private static CompletableFuture executeCommand(BaseClient client, Strin * @param args The command line. * @param returnsMap - true if command returns a map */ - private static CompletableFuture executeCommand( + @SuppressWarnings({"unchecked", "SameParameterValue"}) + private static CompletableFuture executeCommand( BaseClient client, String[] args, boolean returnsMap) { if (client instanceof GlideClient) { - return ((GlideClient) client).customCommand(args); + return ((GlideClient) client).customCommand(args).thenApply(r -> (T) r); } else if (client instanceof GlideClusterClient) { return ((GlideClusterClient) client) .customCommand(args) - .thenApply(returnsMap ? ClusterValue::getMultiValue : ClusterValue::getSingleValue); + .thenApply(returnsMap ? ClusterValue::getMultiValue : ClusterValue::getSingleValue) + .thenApply(r -> (T) r); } throw new IllegalArgumentException( "Unknown type of client, should be either `GlideClient` or `GlideClusterClient`"); @@ -385,7 +392,7 @@ private static CompletableFuture executeCommand( * @param client The client to execute the command. * @param args The command line. */ - private static CompletableFuture executeCommand(BaseClient client, GlideString[] args) { + private static CompletableFuture executeCommand(BaseClient client, GlideString[] args) { return executeCommand(client, args, false); } @@ -396,14 +403,16 @@ private static CompletableFuture executeCommand(BaseClient client, Glide * @param args The command line. * @param returnsMap - true if command returns a map */ - private static CompletableFuture executeCommand( + @SuppressWarnings({"unchecked", "SameParameterValue"}) + private static CompletableFuture executeCommand( BaseClient client, GlideString[] args, boolean returnsMap) { if (client instanceof GlideClient) { - return ((GlideClient) client).customCommand(args); + return ((GlideClient) client).customCommand(args).thenApply(r -> (T) r); } else if (client instanceof GlideClusterClient) { return ((GlideClusterClient) client) .customCommand(args) - .thenApply(returnsMap ? ClusterValue::getMultiValue : ClusterValue::getSingleValue); + .thenApply(returnsMap ? ClusterValue::getMultiValue : ClusterValue::getSingleValue) + .thenApply(r -> (T) r); } throw new IllegalArgumentException( "Unknown type of client, should be either `GlideClient` or `GlideClusterClient`"); diff --git a/java/client/src/main/java/glide/api/models/commands/json/JsonGetOptions.java b/java/client/src/main/java/glide/api/models/commands/json/JsonGetOptions.java index c7f719a7e7..5273e9c8c1 100644 --- a/java/client/src/main/java/glide/api/models/commands/json/JsonGetOptions.java +++ b/java/client/src/main/java/glide/api/models/commands/json/JsonGetOptions.java @@ -1,14 +1,12 @@ /** Copyright Valkey GLIDE Project Contributors - SPDX Identifier: Apache-2.0 */ package glide.api.models.commands.json; -import glide.api.commands.servermodules.GlideJson; +import glide.api.commands.servermodules.Json; import java.util.ArrayList; import java.util.List; import lombok.Builder; -import lombok.Getter; -/** Additional parameters for {@link GlideJson#get} command. */ -@Getter +/** Additional parameters for {@link Json#get} command. */ @Builder public final class JsonGetOptions { /** ValKey API string to designate INDENT */ @@ -20,14 +18,20 @@ public final class JsonGetOptions { /** ValKey API string to designate SPACE */ public static final String SPACE_VALKEY_API = "SPACE"; - /** Sets an indentation string for nested levels. Defaults to null. */ - @Builder.Default private String indent = null; + /** ValKey API string to designate SPACE */ + public static final String NOESCAPE_VALKEY_API = "NOESCAPE"; + + /** Sets an indentation string for nested levels. */ + private String indent; + + /** Sets a string that's printed at the end of each line. */ + private String newline; - /** Sets a string that's printed at the end of each line. Defaults to null. */ - @Builder.Default private String newline = null; + /** Sets a string that's put between a key and a value. */ + private String space; - /** Sets a string that's put between a key and a value. Defaults to null. */ - @Builder.Default private String space = null; + /** Allowed to be present for legacy compatibility and has no other effect. */ + private boolean noescape; /** * Converts JsonGetOptions into a String[]. @@ -35,22 +39,26 @@ public final class JsonGetOptions { * @return String[] */ public String[] toArgs() { - List resultList = new ArrayList<>(); + List args = new ArrayList<>(); if (indent != null) { - resultList.add(INDENT_VALKEY_API); - resultList.add(indent); + args.add(INDENT_VALKEY_API); + args.add(indent); } if (newline != null) { - resultList.add(NEWLINE_VALKEY_API); - resultList.add(newline); + args.add(NEWLINE_VALKEY_API); + args.add(newline); } if (space != null) { - resultList.add(SPACE_VALKEY_API); - resultList.add(space); + args.add(SPACE_VALKEY_API); + args.add(space); + } + + if (noescape) { + args.add(NOESCAPE_VALKEY_API); } - return resultList.toArray(new String[0]); + return args.toArray(new String[0]); } } diff --git a/java/client/src/test/java/glide/api/commands/servermodules/GlideJsonTest.java b/java/client/src/test/java/glide/api/commands/servermodules/JsonTest.java similarity index 70% rename from java/client/src/test/java/glide/api/commands/servermodules/GlideJsonTest.java rename to java/client/src/test/java/glide/api/commands/servermodules/JsonTest.java index 6357d5ebb2..7ff9520ac9 100644 --- a/java/client/src/test/java/glide/api/commands/servermodules/GlideJsonTest.java +++ b/java/client/src/test/java/glide/api/commands/servermodules/JsonTest.java @@ -23,7 +23,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -class GlideJsonTest { +class JsonTest { private GlideClient glideClient; @@ -43,12 +43,12 @@ void set_returns_success() { String expectedResponseValue = "OK"; expectedResponse.complete(expectedResponseValue); when(glideClient - .customCommand(eq(new String[] {GlideJson.JSON_SET, key, path, jsonValue})) + .customCommand(eq(new String[] {Json.JSON_SET, key, path, jsonValue})) .thenApply(any())) .thenReturn(expectedResponse); // exercise - CompletableFuture actualResponse = GlideJson.set(glideClient, key, path, jsonValue); + CompletableFuture actualResponse = Json.set(glideClient, key, path, jsonValue); String actualResponseValue = actualResponse.get(); // verify @@ -67,12 +67,12 @@ void set_binary_returns_success() { String expectedResponseValue = "OK"; expectedResponse.complete(expectedResponseValue); when(glideClient - .customCommand(eq(new GlideString[] {gs(GlideJson.JSON_SET), key, path, jsonValue})) + .customCommand(eq(new GlideString[] {gs(Json.JSON_SET), key, path, jsonValue})) .thenApply(any())) .thenReturn(expectedResponse); // exercise - CompletableFuture actualResponse = GlideJson.set(glideClient, key, path, jsonValue); + CompletableFuture actualResponse = Json.set(glideClient, key, path, jsonValue); String actualResponseValue = actualResponse.get(); // verify @@ -93,16 +93,13 @@ void set_with_condition_returns_success() { expectedResponse.complete(expectedResponseValue); when(glideClient .customCommand( - eq( - new String[] { - GlideJson.JSON_SET, key, path, jsonValue, setCondition.getValkeyApi() - })) + eq(new String[] {Json.JSON_SET, key, path, jsonValue, setCondition.getValkeyApi()})) .thenApply(any())) .thenReturn(expectedResponse); // exercise CompletableFuture actualResponse = - GlideJson.set(glideClient, key, path, jsonValue, setCondition); + Json.set(glideClient, key, path, jsonValue, setCondition); String actualResponseValue = actualResponse.get(); // verify @@ -125,14 +122,14 @@ void set_binary_with_condition_returns_success() { .customCommand( eq( new GlideString[] { - gs(GlideJson.JSON_SET), key, path, jsonValue, gs(setCondition.getValkeyApi()) + gs(Json.JSON_SET), key, path, jsonValue, gs(setCondition.getValkeyApi()) })) .thenApply(any())) .thenReturn(expectedResponse); // exercise CompletableFuture actualResponse = - GlideJson.set(glideClient, key, path, jsonValue, setCondition); + Json.set(glideClient, key, path, jsonValue, setCondition); String actualResponseValue = actualResponse.get(); // verify @@ -146,15 +143,17 @@ void get_with_single_path_returns_success() { // setup String key = "testKey"; String path = "$"; - CompletableFuture expectedResponse = new CompletableFuture<>(); + CompletableFuture expectedResponse = new CompletableFuture<>(); String expectedResponseValue = "{\"a\": 1.0, \"b\": 2}"; expectedResponse.complete(expectedResponseValue); - when(glideClient.customCommand(eq(new String[] {GlideJson.JSON_GET, key, path}))) + when(glideClient + .customCommand(eq(new String[] {Json.JSON_GET, key, path})) + .thenApply(any())) .thenReturn(expectedResponse); // exercise - CompletableFuture actualResponse = GlideJson.get(glideClient, key, path); - String actualResponseValue = (String) actualResponse.get(); + CompletableFuture actualResponse = Json.get(glideClient, key, path); + String actualResponseValue = actualResponse.get(); // verify assertEquals(expectedResponse, actualResponse); @@ -167,15 +166,17 @@ void get_binary_with_single_path_returns_success() { // setup GlideString key = gs("testKey"); GlideString path = gs("$"); - CompletableFuture expectedResponse = new CompletableFuture<>(); + CompletableFuture expectedResponse = new CompletableFuture<>(); GlideString expectedResponseValue = gs("{\"a\": 1.0, \"b\": 2}"); expectedResponse.complete(expectedResponseValue); - when(glideClient.customCommand(eq(new GlideString[] {gs(GlideJson.JSON_GET), key, path}))) + when(glideClient + .customCommand(eq(new GlideString[] {gs(Json.JSON_GET), key, path})) + .thenApply(any())) .thenReturn(expectedResponse); // exercise - CompletableFuture actualResponse = GlideJson.get(glideClient, key, path); - GlideString actualResponseValue = (GlideString) actualResponse.get(); + CompletableFuture actualResponse = Json.get(glideClient, key, path); + GlideString actualResponseValue = actualResponse.get(); // verify assertEquals(expectedResponse, actualResponse); @@ -190,15 +191,17 @@ void get_with_multiple_paths_returns_success() { String path1 = ".firstName"; String path2 = ".lastName"; String[] paths = new String[] {path1, path2}; - CompletableFuture expectedResponse = new CompletableFuture<>(); + CompletableFuture expectedResponse = new CompletableFuture<>(); String expectedResponseValue = "{\"a\": 1.0, \"b\": 2}"; expectedResponse.complete(expectedResponseValue); - when(glideClient.customCommand(eq(new String[] {GlideJson.JSON_GET, key, path1, path2}))) + when(glideClient + .customCommand(eq(new String[] {Json.JSON_GET, key, path1, path2})) + .thenApply(any())) .thenReturn(expectedResponse); // exercise - CompletableFuture actualResponse = GlideJson.get(glideClient, key, paths); - String actualResponseValue = (String) actualResponse.get(); + CompletableFuture actualResponse = Json.get(glideClient, key, paths); + String actualResponseValue = actualResponse.get(); // verify assertEquals(expectedResponse, actualResponse); @@ -213,16 +216,17 @@ void get_binary_with_multiple_paths_returns_success() { GlideString path1 = gs(".firstName"); GlideString path2 = gs(".lastName"); GlideString[] paths = new GlideString[] {path1, path2}; - CompletableFuture expectedResponse = new CompletableFuture<>(); + CompletableFuture expectedResponse = new CompletableFuture<>(); GlideString expectedResponseValue = gs("{\"a\": 1.0, \"b\": 2}"); expectedResponse.complete(expectedResponseValue); - when(glideClient.customCommand( - eq(new GlideString[] {gs(GlideJson.JSON_GET), key, path1, path2}))) + when(glideClient + .customCommand(eq(new GlideString[] {gs(Json.JSON_GET), key, path1, path2})) + .thenApply(any())) .thenReturn(expectedResponse); // exercise - CompletableFuture actualResponse = GlideJson.get(glideClient, key, paths); - GlideString actualResponseValue = (GlideString) actualResponse.get(); + CompletableFuture actualResponse = Json.get(glideClient, key, paths); + GlideString actualResponseValue = actualResponse.get(); // verify assertEquals(expectedResponse, actualResponse); @@ -236,19 +240,21 @@ void get_with_single_path_and_options_returns_success() { String key = "testKey"; String path = "$"; JsonGetOptions options = JsonGetOptions.builder().indent("\t").space(" ").newline("\n").build(); - CompletableFuture expectedResponse = new CompletableFuture<>(); + CompletableFuture expectedResponse = new CompletableFuture<>(); String expectedResponseValue = "{\"a\": 1.0, \"b\": 2}"; expectedResponse.complete(expectedResponseValue); - when(glideClient.customCommand( - eq( - ArrayUtils.add( - ArrayUtils.addAll(new String[] {GlideJson.JSON_GET, key}, options.toArgs()), - path)))) + when(glideClient + .customCommand( + eq( + ArrayUtils.add( + ArrayUtils.addAll(new String[] {Json.JSON_GET, key}, options.toArgs()), + path))) + .thenApply(any())) .thenReturn(expectedResponse); // exercise - CompletableFuture actualResponse = GlideJson.get(glideClient, key, path, options); - String actualResponseValue = (String) actualResponse.get(); + CompletableFuture actualResponse = Json.get(glideClient, key, path, options); + String actualResponseValue = actualResponse.get(); // verify assertEquals(expectedResponse, actualResponse); @@ -262,21 +268,23 @@ void get_binary_with_single_path_and_options_returns_success() { GlideString key = gs("testKey"); GlideString path = gs("$"); JsonGetOptions options = JsonGetOptions.builder().indent("\t").space(" ").newline("\n").build(); - CompletableFuture expectedResponse = new CompletableFuture<>(); + CompletableFuture expectedResponse = new CompletableFuture<>(); GlideString expectedResponseValue = gs("{\"a\": 1.0, \"b\": 2}"); expectedResponse.complete(expectedResponseValue); - when(glideClient.customCommand( - eq( - new ArgsBuilder() - .add(new GlideString[] {gs(GlideJson.JSON_GET), key}) - .add(options.toArgs()) - .add(path) - .toArray()))) + when(glideClient + .customCommand( + eq( + new ArgsBuilder() + .add(new GlideString[] {gs(Json.JSON_GET), key}) + .add(options.toArgs()) + .add(path) + .toArray())) + .thenApply(any())) .thenReturn(expectedResponse); // exercise - CompletableFuture actualResponse = GlideJson.get(glideClient, key, path, options); - GlideString actualResponseValue = (GlideString) actualResponse.get(); + CompletableFuture actualResponse = Json.get(glideClient, key, path, options); + GlideString actualResponseValue = actualResponse.get(); // verify assertEquals(expectedResponse, actualResponse); @@ -292,20 +300,20 @@ void get_with_multiple_paths_and_options_returns_success() { String path2 = ".lastName"; JsonGetOptions options = JsonGetOptions.builder().indent("\t").newline("\n").space(" ").build(); String[] paths = new String[] {path1, path2}; - CompletableFuture expectedResponse = new CompletableFuture<>(); + CompletableFuture expectedResponse = new CompletableFuture<>(); String expectedResponseValue = "{\"a\": 1.0, \"b\": 2}"; expectedResponse.complete(expectedResponseValue); ArrayList argsList = new ArrayList<>(); - argsList.add(GlideJson.JSON_GET); + argsList.add(Json.JSON_GET); argsList.add(key); Collections.addAll(argsList, options.toArgs()); Collections.addAll(argsList, paths); - when(glideClient.customCommand(eq(argsList.toArray(new String[0])))) + when(glideClient.customCommand(eq(argsList.toArray(new String[0]))).thenApply(any())) .thenReturn(expectedResponse); // exercise - CompletableFuture actualResponse = GlideJson.get(glideClient, key, paths, options); - String actualResponseValue = (String) actualResponse.get(); + CompletableFuture actualResponse = Json.get(glideClient, key, paths, options); + String actualResponseValue = actualResponse.get(); // verify assertArrayEquals( @@ -323,21 +331,17 @@ void get_binary_with_multiple_paths_and_options_returns_success() { GlideString path2 = gs(".lastName"); JsonGetOptions options = JsonGetOptions.builder().indent("\t").newline("\n").space(" ").build(); GlideString[] paths = new GlideString[] {path1, path2}; - CompletableFuture expectedResponse = new CompletableFuture<>(); + CompletableFuture expectedResponse = new CompletableFuture<>(); GlideString expectedResponseValue = gs("{\"a\": 1.0, \"b\": 2}"); expectedResponse.complete(expectedResponseValue); GlideString[] args = - new ArgsBuilder() - .add(GlideJson.JSON_GET) - .add(key) - .add(options.toArgs()) - .add(paths) - .toArray(); - when(glideClient.customCommand(eq(args))).thenReturn(expectedResponse); + new ArgsBuilder().add(Json.JSON_GET).add(key).add(options.toArgs()).add(paths).toArray(); + when(glideClient.customCommand(eq(args)).thenApply(any())) + .thenReturn(expectedResponse); // exercise - CompletableFuture actualResponse = GlideJson.get(glideClient, key, paths, options); - GlideString actualResponseValue = (GlideString) actualResponse.get(); + CompletableFuture actualResponse = Json.get(glideClient, key, paths, options); + GlideString actualResponseValue = actualResponse.get(); // verify assertEquals(expectedResponse, actualResponse); diff --git a/java/integTest/src/test/java/glide/modules/JsonTests.java b/java/integTest/src/test/java/glide/modules/JsonTests.java index c5eb87e760..32912b7c73 100644 --- a/java/integTest/src/test/java/glide/modules/JsonTests.java +++ b/java/integTest/src/test/java/glide/modules/JsonTests.java @@ -7,12 +7,11 @@ import static glide.api.models.configuration.RequestRoutingConfiguration.SimpleMultiNodeRoute.ALL_PRIMARIES; import static glide.api.models.configuration.RequestRoutingConfiguration.SimpleSingleNodeRoute.RANDOM; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertInstanceOf; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; import glide.api.GlideClusterClient; -import glide.api.commands.servermodules.GlideJson; +import glide.api.commands.servermodules.Json; import glide.api.models.GlideString; import glide.api.models.commands.ConditionalChange; import glide.api.models.commands.FlushMode; @@ -58,21 +57,19 @@ public void json_set_get() { String key = UUID.randomUUID().toString(); String jsonValue = "{\"a\": 1.0,\"b\": 2}"; - assertEquals(OK, GlideJson.set(client, key, "$", jsonValue).get()); + assertEquals(OK, Json.set(client, key, "$", jsonValue).get()); - Object getResult = GlideJson.get(client, key, ".").get(); + String getResult = Json.get(client, key, ".").get(); - assertInstanceOf(String.class, getResult); - JSONAssert.assertEquals(jsonValue, (String) getResult, JSONCompareMode.LENIENT); + JSONAssert.assertEquals(jsonValue, getResult, JSONCompareMode.LENIENT); - Object getResultWithMultiPaths = GlideJson.get(client, key, new String[] {"$.a", "$.b"}).get(); + String getResultWithMultiPaths = Json.get(client, key, new String[] {"$.a", "$.b"}).get(); - assertInstanceOf(String.class, getResultWithMultiPaths); JSONAssert.assertEquals( - "{\"$.a\":[1.0],\"$.b\":[2]}", (String) getResultWithMultiPaths, JSONCompareMode.LENIENT); + "{\"$.a\":[1.0],\"$.b\":[2]}", getResultWithMultiPaths, JSONCompareMode.LENIENT); - assertNull(GlideJson.get(client, "non_existing_key", "$").get()); - assertEquals("[]", GlideJson.get(client, key, "$.d").get()); + assertNull(Json.get(client, "non_existing_key", "$").get()); + assertEquals("[]", Json.get(client, key, "$.d").get()); } @Test @@ -81,28 +78,24 @@ public void json_set_get_multiple_values() { String key = UUID.randomUUID().toString(); String jsonValue = "{\"a\": {\"c\": 1, \"d\": 4}, \"b\": {\"c\": 2}, \"c\": true}"; - assertEquals(OK, GlideJson.set(client, gs(key), gs("$"), gs(jsonValue)).get()); + assertEquals(OK, Json.set(client, gs(key), gs("$"), gs(jsonValue)).get()); - Object getResult = GlideJson.get(client, gs(key), gs("$..c")).get(); + GlideString getResult = Json.get(client, gs(key), gs("$..c")).get(); - assertInstanceOf(GlideString.class, getResult); - JSONAssert.assertEquals( - "[true, 1, 2]", ((GlideString) getResult).getString(), JSONCompareMode.LENIENT); + JSONAssert.assertEquals("[true, 1, 2]", getResult.getString(), JSONCompareMode.LENIENT); - Object getResultWithMultiPaths = GlideJson.get(client, key, new String[] {"$..c", "$.c"}).get(); + String getResultWithMultiPaths = Json.get(client, key, new String[] {"$..c", "$.c"}).get(); - assertInstanceOf(String.class, getResultWithMultiPaths); JSONAssert.assertEquals( "{\"$..c\": [True, 1, 2], \"$.c\": [True]}", - (String) getResultWithMultiPaths, + getResultWithMultiPaths, JSONCompareMode.LENIENT); - assertEquals(OK, GlideJson.set(client, key, "$..c", "\"new_value\"").get()); - Object getResultAfterSetNewValue = GlideJson.get(client, key, "$..c").get(); - assertInstanceOf(String.class, getResultAfterSetNewValue); + assertEquals(OK, Json.set(client, key, "$..c", "\"new_value\"").get()); + String getResultAfterSetNewValue = Json.get(client, key, "$..c").get(); JSONAssert.assertEquals( "[\"new_value\", \"new_value\", \"new_value\"]", - (String) getResultAfterSetNewValue, + getResultAfterSetNewValue, JSONCompareMode.LENIENT); } @@ -112,16 +105,13 @@ public void json_set_get_conditional_set() { String key = UUID.randomUUID().toString(); String jsonValue = "{\"a\": 1.0, \"b\": 2}"; - assertNull(GlideJson.set(client, key, "$", jsonValue, ConditionalChange.ONLY_IF_EXISTS).get()); - assertEquals( - OK, - GlideJson.set(client, key, "$", jsonValue, ConditionalChange.ONLY_IF_DOES_NOT_EXIST).get()); - assertNull( - GlideJson.set(client, key, "$.a", "4.5", ConditionalChange.ONLY_IF_DOES_NOT_EXIST).get()); - assertEquals("1.0", (String) GlideJson.get(client, key, ".a").get()); + assertNull(Json.set(client, key, "$", jsonValue, ConditionalChange.ONLY_IF_EXISTS).get()); assertEquals( - OK, GlideJson.set(client, key, "$.a", "4.5", ConditionalChange.ONLY_IF_EXISTS).get()); - assertEquals("4.5", GlideJson.get(client, key, ".a").get()); + OK, Json.set(client, key, "$", jsonValue, ConditionalChange.ONLY_IF_DOES_NOT_EXIST).get()); + assertNull(Json.set(client, key, "$.a", "4.5", ConditionalChange.ONLY_IF_DOES_NOT_EXIST).get()); + assertEquals("1.0", Json.get(client, key, ".a").get()); + assertEquals(OK, Json.set(client, key, "$.a", "4.5", ConditionalChange.ONLY_IF_EXISTS).get()); + assertEquals("4.5", Json.get(client, key, ".a").get()); } @Test @@ -131,8 +121,7 @@ public void json_set_get_formatting() { assertEquals( OK, - GlideJson.set(client, key, "$", "{\"a\": 1.0, \"b\": 2, \"c\": {\"d\": 3, \"e\": 4}}") - .get()); + Json.set(client, key, "$", "{\"a\": 1.0, \"b\": 2, \"c\": {\"d\": 3, \"e\": 4}}").get()); String expectedGetResult = "[\n" @@ -145,8 +134,8 @@ public void json_set_get_formatting() { + " }\n" + " }\n" + "]"; - Object actualGetResult = - GlideJson.get( + String actualGetResult = + Json.get( client, key, "$", @@ -156,8 +145,8 @@ public void json_set_get_formatting() { String expectedGetResult2 = "[\n~{\n~~\"a\":*1.0,\n~~\"b\":*2,\n~~\"c\":*{\n~~~\"d\":*3,\n~~~\"e\":*4\n~~}\n~}\n]"; - Object actualGetResult2 = - GlideJson.get( + String actualGetResult2 = + Json.get( client, key, "$", From 01f6b53b4f7756d818487ce8ebc5ca474f38371e Mon Sep 17 00:00:00 2001 From: James Xin Date: Thu, 17 Oct 2024 11:44:28 -0700 Subject: [PATCH 6/7] address comment 2 Signed-off-by: James Xin --- .../api/commands/servermodules/Json.java | 148 +++++++++++------- .../commands/json/JsonGetOptionsBinary.java | 67 ++++++++ .../api/commands/servermodules/JsonTest.java | 41 +++-- .../test/java/glide/modules/JsonTests.java | 18 +-- 4 files changed, 186 insertions(+), 88 deletions(-) create mode 100644 java/client/src/main/java/glide/api/models/commands/json/JsonGetOptionsBinary.java diff --git a/java/client/src/main/java/glide/api/commands/servermodules/Json.java b/java/client/src/main/java/glide/api/commands/servermodules/Json.java index 053c1e6695..2b24ce4faa 100644 --- a/java/client/src/main/java/glide/api/commands/servermodules/Json.java +++ b/java/client/src/main/java/glide/api/commands/servermodules/Json.java @@ -10,6 +10,7 @@ import glide.api.models.GlideString; import glide.api.models.commands.ConditionalChange; import glide.api.models.commands.json.JsonGetOptions; +import glide.api.models.commands.json.JsonGetOptionsBinary; import glide.utils.ArgsBuilder; import glide.utils.ArrayTransformUtils; import java.util.concurrent.CompletableFuture; @@ -138,21 +139,16 @@ public static CompletableFuture set( * * @param client The Valkey GLIDE client to execute the command. * @param key The key of the JSON document. - * @param path The path within the JSON document. - * @return For JSONPath (path starts with $): Returns a stringifies JSON list of - * bytes replies for every possible path, or a byte string representation of an empty array, - * if path doesn't exist. For legacy path (path doesn't start with $): Returns a - * byte string representation of the value in path. If path doesn't - * exist, an error is raised. If key doesn't exist, returns null. + * @return Returns a string representation of the JSON document. If key doesn't + * exist, returns null. * @example *
{@code
-     * String value = client.Json.get(client, "doc", "$").get();
+     * String value = client.Json.get(client, "doc").get();
      * assert value.equals("{'a': 1.0, 'b': 2}");
      * }
*/ - public static CompletableFuture get( - @NonNull BaseClient client, @NonNull String key, @NonNull String path) { - return executeCommand(client, new String[] {JSON_GET, key, path}); + public static CompletableFuture get(@NonNull BaseClient client, @NonNull String key) { + return executeCommand(client, new String[] {JSON_GET, key}); } /** @@ -160,21 +156,17 @@ public static CompletableFuture get( * * @param client The Valkey GLIDE client to execute the command. * @param key The key of the JSON document. - * @param path The path within the JSON document. - * @return For JSONPath (path starts with $): Returns a stringifies JSON list of - * bytes replies for every possible path, or a byte string representation of an empty array, - * if path doesn't exist. For legacy path (path doesn't start with $): Returns a - * byte string representation of the value in path. If path doesn't - * exist, an error is raised. If key doesn't exist, returns null. + * @return Returns a string representation of the JSON document. If key doesn't + * exist, returns null. * @example *
{@code
-     * GlideString value = client.Json.get(client, gs("doc"), gs("$")).get();
+     * GlideString value = client.Json.get(client, gs("doc")).get();
      * assert value.equals(gs("{'a': 1.0, 'b': 2}"));
      * }
*/ public static CompletableFuture get( - @NonNull BaseClient client, @NonNull GlideString key, @NonNull GlideString path) { - return executeCommand(client, new GlideString[] {gs(JSON_GET), key, path}); + @NonNull BaseClient client, @NonNull GlideString key) { + return executeCommand(client, new GlideString[] {gs(JSON_GET), key}); } /** @@ -183,9 +175,24 @@ public static CompletableFuture get( * @param client The Valkey GLIDE client to execute the command. * @param key The key of the JSON document. * @param paths List of paths within the JSON document. - * @return Returns a stringifies JSON object in bytes, in which each path is a key, and it's - * corresponding value, is the value as if the path was executed in the command as a single - * path. If key doesn't exist, returns null. + * @return + *
    + *
  • If one path is given: + *
      + *
    • For JSONPath (path starts with $): Returns a stringified JSON list + * replies for every possible path, or a string representation of an empty array, + * if path doesn't exist. If key doesn't exist, returns None. + *
    • For legacy path (path doesn't start with $): Returns a string + * representation of the value in paths. If paths + * doesn't exist, an error is raised. If key doesn't exist, returns + * None. + *
    + *
  • If multiple paths are given: Returns a stringified JSON, in which each path is a key, + * and it's corresponding value, is the value as if the path was executed in the command + * as a single path. + *
+ * In case of multiple paths, and paths are a mix of both JSONPath and legacy + * path, the command behaves as if all are JSONPath paths. * @example *
{@code
      * String value = client.Json.get(client, "doc", new String[] {"$.a", "$.b"}).get();
@@ -204,9 +211,24 @@ public static CompletableFuture get(
      * @param client The Valkey GLIDE client to execute the command.
      * @param key The key of the JSON document.
      * @param paths List of paths within the JSON document.
-     * @return Returns a stringifies JSON object in bytes, in which each path is a key, and it's
-     *     corresponding value, is the value as if the path was executed in the command as a single
-     *     path. If key doesn't exist, returns null.
+     * @return
+     *     
    + *
  • If one path is given: + *
      + *
    • For JSONPath (path starts with $): Returns a stringified JSON list + * replies for every possible path, or a string representation of an empty array, + * if path doesn't exist. If key doesn't exist, returns None. + *
    • For legacy path (path doesn't start with $): Returns a string + * representation of the value in paths. If paths + * doesn't exist, an error is raised. If key doesn't exist, returns + * None. + *
    + *
  • If multiple paths are given: Returns a stringified JSON, in which each path is a key, + * and it's corresponding value, is the value as if the path was executed in the command + * as a single path. + *
+ * In case of multiple paths, and paths are a mix of both JSONPath and legacy + * path, the command behaves as if all are JSONPath paths. * @example *
{@code
      * GlideString value = client.Json.get(client, gs("doc"), new GlideString[] {gs("$.a"), gs("$.b")}).get();
@@ -225,14 +247,10 @@ public static CompletableFuture get(
      *
      * @param client The Valkey GLIDE client to execute the command.
      * @param key The key of the JSON document.
-     * @param path The path within the JSON document.
      * @param options Options for formatting the byte representation of the JSON data. See 
      *     JsonGetOptions.
-     * @return For JSONPath (path starts with $): Returns a stringifies JSON list of
-     *     bytes replies for every possible path, or a byte string representation of an empty array,
-     *     if path doesn't exist. For legacy path (path doesn't start with $): Returns a
-     *     byte string representation of the value in path. If path doesn't
-     *     exist, an error is raised. If key doesn't exist, returns null.
+     * @return Returns a string representation of the JSON document. If key doesn't
+     *     exist, returns null.
      * @example
      *     
{@code
      * JsonGetOptions options = JsonGetOptions.builder()
@@ -245,14 +263,10 @@ public static CompletableFuture get(
      * }
*/ public static CompletableFuture get( - @NonNull BaseClient client, - @NonNull String key, - @NonNull String path, - @NonNull JsonGetOptions options) { + @NonNull BaseClient client, @NonNull String key, @NonNull JsonGetOptions options) { return executeCommand( client, - ArrayTransformUtils.concatenateArrays( - new String[] {JSON_GET, key}, options.toArgs(), new String[] {path})); + ArrayTransformUtils.concatenateArrays(new String[] {JSON_GET, key}, options.toArgs())); } /** @@ -260,14 +274,10 @@ public static CompletableFuture get( * * @param client The Valkey GLIDE client to execute the command. * @param key The key of the JSON document. - * @param path The path within the JSON document. * @param options Options for formatting the byte representation of the JSON data. See * JsonGetOptions. - * @return For JSONPath (path starts with $): Returns a stringifies JSON list of - * bytes replies for every possible path, or a byte string representation of an empty array, - * if path doesn't exist. For legacy path (path doesn't start with $): Returns a - * byte string representation of the value in path. If path doesn't - * exist, an error is raised. If key doesn't exist, returns null. + * @return Returns a string representation of the JSON document. If key doesn't + * exist, returns null. * @example *
{@code
      * JsonGetOptions options = JsonGetOptions.builder()
@@ -280,13 +290,9 @@ public static CompletableFuture get(
      * }
*/ public static CompletableFuture get( - @NonNull BaseClient client, - @NonNull GlideString key, - @NonNull GlideString path, - @NonNull JsonGetOptions options) { + @NonNull BaseClient client, @NonNull GlideString key, @NonNull JsonGetOptionsBinary options) { return executeCommand( - client, - new ArgsBuilder().add(gs(JSON_GET)).add(key).add(options.toArgs()).add(path).toArray()); + client, new ArgsBuilder().add(gs(JSON_GET)).add(key).add(options.toArgs()).toArray()); } /** @@ -297,9 +303,24 @@ public static CompletableFuture get( * @param paths List of paths within the JSON document. * @param options Options for formatting the byte representation of the JSON data. See * JsonGetOptions. - * @return Returns a stringifies JSON object in bytes, in which each path is a key, and it's - * corresponding value, is the value as if the path was executed in the command as a single - * path. If key doesn't exist, returns null. + * @return + *
    + *
  • If one path is given: + *
      + *
    • For JSONPath (path starts with $): Returns a stringified JSON list + * replies for every possible path, or a string representation of an empty array, + * if path doesn't exist. If key doesn't exist, returns None. + *
    • For legacy path (path doesn't start with $): Returns a string + * representation of the value in paths. If paths + * doesn't exist, an error is raised. If key doesn't exist, returns + * None. + *
    + *
  • If multiple paths are given: Returns a stringified JSON, in which each path is a key, + * and it's corresponding value, is the value as if the path was executed in the command + * as a single path. + *
+ * In case of multiple paths, and paths are a mix of both JSONPath and legacy + * path, the command behaves as if all are JSONPath paths. * @example *
{@code
      * JsonGetOptions options = JsonGetOptions.builder()
@@ -330,9 +351,24 @@ public static CompletableFuture get(
      * @param paths List of paths within the JSON document.
      * @param options Options for formatting the byte representation of the JSON data. See 
      *     JsonGetOptions.
-     * @return Returns a stringifies JSON object in bytes, in which each path is a key, and it's
-     *     corresponding value, is the value as if the path was executed in the command as a single
-     *     path. If key doesn't exist, returns null.
+     * @return
+     *     
    + *
  • If one path is given: + *
      + *
    • For JSONPath (path starts with $): Returns a stringified JSON list + * replies for every possible path, or a string representation of an empty array, + * if path doesn't exist. If key doesn't exist, returns None. + *
    • For legacy path (path doesn't start with $): Returns a string + * representation of the value in paths. If paths + * doesn't exist, an error is raised. If key doesn't exist, returns + * None. + *
    + *
  • If multiple paths are given: Returns a stringified JSON, in which each path is a key, + * and it's corresponding value, is the value as if the path was executed in the command + * as a single path. + *
+ * In case of multiple paths, and paths are a mix of both JSONPath and legacy + * path, the command behaves as if all are JSONPath paths. * @example *
{@code
      * JsonGetOptions options = JsonGetOptions.builder()
@@ -348,7 +384,7 @@ public static CompletableFuture get(
             @NonNull BaseClient client,
             @NonNull GlideString key,
             @NonNull GlideString[] paths,
-            @NonNull JsonGetOptions options) {
+            @NonNull JsonGetOptionsBinary options) {
         return executeCommand(
                 client,
                 new ArgsBuilder().add(gs(JSON_GET)).add(key).add(options.toArgs()).add(paths).toArray());
diff --git a/java/client/src/main/java/glide/api/models/commands/json/JsonGetOptionsBinary.java b/java/client/src/main/java/glide/api/models/commands/json/JsonGetOptionsBinary.java
new file mode 100644
index 0000000000..634b4d298e
--- /dev/null
+++ b/java/client/src/main/java/glide/api/models/commands/json/JsonGetOptionsBinary.java
@@ -0,0 +1,67 @@
+/** Copyright Valkey GLIDE Project Contributors - SPDX Identifier: Apache-2.0 */
+package glide.api.models.commands.json;
+
+import static glide.api.models.GlideString.gs;
+
+import glide.api.commands.servermodules.Json;
+import glide.api.models.GlideString;
+import java.util.ArrayList;
+import java.util.List;
+import lombok.Builder;
+
+/** GlideString version of additional parameters for {@link Json#get} command. */
+@Builder
+public final class JsonGetOptionsBinary {
+    /** ValKey API string to designate INDENT */
+    public static final GlideString INDENT_VALKEY_API = gs("INDENT");
+
+    /** ValKey API string to designate NEWLINE */
+    public static final GlideString NEWLINE_VALKEY_API = gs("NEWLINE");
+
+    /** ValKey API string to designate SPACE */
+    public static final GlideString SPACE_VALKEY_API = gs("SPACE");
+
+    /** ValKey API string to designate SPACE */
+    public static final GlideString NOESCAPE_VALKEY_API = gs("NOESCAPE");
+
+    /** Sets an indentation string for nested levels. */
+    private GlideString indent;
+
+    /** Sets a string that's printed at the end of each line. */
+    private GlideString newline;
+
+    /** Sets a string that's put between a key and a value. */
+    private GlideString space;
+
+    /** Allowed to be present for legacy compatibility and has no other effect. */
+    private boolean noescape;
+
+    /**
+     * Converts JsonGetOptions into a GlideString[].
+     *
+     * @return GlideString[]
+     */
+    public GlideString[] toArgs() {
+        List args = new ArrayList<>();
+        if (indent != null) {
+            args.add(INDENT_VALKEY_API);
+            args.add(indent);
+        }
+
+        if (newline != null) {
+            args.add(NEWLINE_VALKEY_API);
+            args.add(newline);
+        }
+
+        if (space != null) {
+            args.add(SPACE_VALKEY_API);
+            args.add(space);
+        }
+
+        if (noescape) {
+            args.add(NOESCAPE_VALKEY_API);
+        }
+
+        return args.toArray(new GlideString[0]);
+    }
+}
diff --git a/java/client/src/test/java/glide/api/commands/servermodules/JsonTest.java b/java/client/src/test/java/glide/api/commands/servermodules/JsonTest.java
index 7ff9520ac9..81754474a3 100644
--- a/java/client/src/test/java/glide/api/commands/servermodules/JsonTest.java
+++ b/java/client/src/test/java/glide/api/commands/servermodules/JsonTest.java
@@ -14,12 +14,13 @@
 import glide.api.models.GlideString;
 import glide.api.models.commands.ConditionalChange;
 import glide.api.models.commands.json.JsonGetOptions;
+import glide.api.models.commands.json.JsonGetOptionsBinary;
 import glide.utils.ArgsBuilder;
+import glide.utils.ArrayTransformUtils;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.concurrent.CompletableFuture;
 import lombok.SneakyThrows;
-import org.apache.commons.lang3.ArrayUtils;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 
@@ -139,20 +140,17 @@ void set_binary_with_condition_returns_success() {
 
     @Test
     @SneakyThrows
-    void get_with_single_path_returns_success() {
+    void get_with_no_path_returns_success() {
         // setup
         String key = "testKey";
-        String path = "$";
         CompletableFuture expectedResponse = new CompletableFuture<>();
         String expectedResponseValue = "{\"a\": 1.0, \"b\": 2}";
         expectedResponse.complete(expectedResponseValue);
-        when(glideClient
-                        .customCommand(eq(new String[] {Json.JSON_GET, key, path}))
-                        .thenApply(any()))
+        when(glideClient.customCommand(eq(new String[] {Json.JSON_GET, key})).thenApply(any()))
                 .thenReturn(expectedResponse);
 
         // exercise
-        CompletableFuture actualResponse = Json.get(glideClient, key, path);
+        CompletableFuture actualResponse = Json.get(glideClient, key);
         String actualResponseValue = actualResponse.get();
 
         // verify
@@ -162,20 +160,19 @@ void get_with_single_path_returns_success() {
 
     @Test
     @SneakyThrows
-    void get_binary_with_single_path_returns_success() {
+    void get_binary_with_no_path_returns_success() {
         // setup
         GlideString key = gs("testKey");
-        GlideString path = gs("$");
         CompletableFuture expectedResponse = new CompletableFuture<>();
         GlideString expectedResponseValue = gs("{\"a\": 1.0, \"b\": 2}");
         expectedResponse.complete(expectedResponseValue);
         when(glideClient
-                        .customCommand(eq(new GlideString[] {gs(Json.JSON_GET), key, path}))
+                        .customCommand(eq(new GlideString[] {gs(Json.JSON_GET), key}))
                         .thenApply(any()))
                 .thenReturn(expectedResponse);
 
         // exercise
-        CompletableFuture actualResponse = Json.get(glideClient, key, path);
+        CompletableFuture actualResponse = Json.get(glideClient, key);
         GlideString actualResponseValue = actualResponse.get();
 
         // verify
@@ -235,10 +232,9 @@ void get_binary_with_multiple_paths_returns_success() {
 
     @Test
     @SneakyThrows
-    void get_with_single_path_and_options_returns_success() {
+    void get_with_no_path_and_options_returns_success() {
         // setup
         String key = "testKey";
-        String path = "$";
         JsonGetOptions options = JsonGetOptions.builder().indent("\t").space(" ").newline("\n").build();
         CompletableFuture expectedResponse = new CompletableFuture<>();
         String expectedResponseValue = "{\"a\": 1.0, \"b\": 2}";
@@ -246,14 +242,13 @@ void get_with_single_path_and_options_returns_success() {
         when(glideClient
                         .customCommand(
                                 eq(
-                                        ArrayUtils.add(
-                                                ArrayUtils.addAll(new String[] {Json.JSON_GET, key}, options.toArgs()),
-                                                path)))
+                                        ArrayTransformUtils.concatenateArrays(
+                                                new String[] {Json.JSON_GET, key}, options.toArgs())))
                         .thenApply(any()))
                 .thenReturn(expectedResponse);
 
         // exercise
-        CompletableFuture actualResponse = Json.get(glideClient, key, path, options);
+        CompletableFuture actualResponse = Json.get(glideClient, key, options);
         String actualResponseValue = actualResponse.get();
 
         // verify
@@ -263,11 +258,11 @@ void get_with_single_path_and_options_returns_success() {
 
     @Test
     @SneakyThrows
-    void get_binary_with_single_path_and_options_returns_success() {
+    void get_binary_with_no_path_and_options_returns_success() {
         // setup
         GlideString key = gs("testKey");
-        GlideString path = gs("$");
-        JsonGetOptions options = JsonGetOptions.builder().indent("\t").space(" ").newline("\n").build();
+        JsonGetOptionsBinary options =
+                JsonGetOptionsBinary.builder().indent(gs("\t")).space(gs(" ")).newline(gs("\n")).build();
         CompletableFuture expectedResponse = new CompletableFuture<>();
         GlideString expectedResponseValue = gs("{\"a\": 1.0, \"b\": 2}");
         expectedResponse.complete(expectedResponseValue);
@@ -277,13 +272,12 @@ void get_binary_with_single_path_and_options_returns_success() {
                                         new ArgsBuilder()
                                                 .add(new GlideString[] {gs(Json.JSON_GET), key})
                                                 .add(options.toArgs())
-                                                .add(path)
                                                 .toArray()))
                         .thenApply(any()))
                 .thenReturn(expectedResponse);
 
         // exercise
-        CompletableFuture actualResponse = Json.get(glideClient, key, path, options);
+        CompletableFuture actualResponse = Json.get(glideClient, key, options);
         GlideString actualResponseValue = actualResponse.get();
 
         // verify
@@ -329,7 +323,8 @@ void get_binary_with_multiple_paths_and_options_returns_success() {
         GlideString key = gs("testKey");
         GlideString path1 = gs(".firstName");
         GlideString path2 = gs(".lastName");
-        JsonGetOptions options = JsonGetOptions.builder().indent("\t").newline("\n").space(" ").build();
+        JsonGetOptionsBinary options =
+                JsonGetOptionsBinary.builder().indent(gs("\t")).newline(gs("\n")).space(gs(" ")).build();
         GlideString[] paths = new GlideString[] {path1, path2};
         CompletableFuture expectedResponse = new CompletableFuture<>();
         GlideString expectedResponseValue = gs("{\"a\": 1.0, \"b\": 2}");
diff --git a/java/integTest/src/test/java/glide/modules/JsonTests.java b/java/integTest/src/test/java/glide/modules/JsonTests.java
index 32912b7c73..3bf1c93823 100644
--- a/java/integTest/src/test/java/glide/modules/JsonTests.java
+++ b/java/integTest/src/test/java/glide/modules/JsonTests.java
@@ -59,7 +59,7 @@ public void json_set_get() {
 
         assertEquals(OK, Json.set(client, key, "$", jsonValue).get());
 
-        String getResult = Json.get(client, key, ".").get();
+        String getResult = Json.get(client, key).get();
 
         JSONAssert.assertEquals(jsonValue, getResult, JSONCompareMode.LENIENT);
 
@@ -68,8 +68,8 @@ public void json_set_get() {
         JSONAssert.assertEquals(
                 "{\"$.a\":[1.0],\"$.b\":[2]}", getResultWithMultiPaths, JSONCompareMode.LENIENT);
 
-        assertNull(Json.get(client, "non_existing_key", "$").get());
-        assertEquals("[]", Json.get(client, key, "$.d").get());
+        assertNull(Json.get(client, "non_existing_key").get());
+        assertEquals("[]", Json.get(client, key, new String[] {"$.d"}).get());
     }
 
     @Test
@@ -80,7 +80,7 @@ public void json_set_get_multiple_values() {
 
         assertEquals(OK, Json.set(client, gs(key), gs("$"), gs(jsonValue)).get());
 
-        GlideString getResult = Json.get(client, gs(key), gs("$..c")).get();
+        GlideString getResult = Json.get(client, gs(key), new GlideString[] {gs("$..c")}).get();
 
         JSONAssert.assertEquals("[true, 1, 2]", getResult.getString(), JSONCompareMode.LENIENT);
 
@@ -92,7 +92,7 @@ public void json_set_get_multiple_values() {
                 JSONCompareMode.LENIENT);
 
         assertEquals(OK, Json.set(client, key, "$..c", "\"new_value\"").get());
-        String getResultAfterSetNewValue = Json.get(client, key, "$..c").get();
+        String getResultAfterSetNewValue = Json.get(client, key, new String[] {"$..c"}).get();
         JSONAssert.assertEquals(
                 "[\"new_value\", \"new_value\", \"new_value\"]",
                 getResultAfterSetNewValue,
@@ -109,9 +109,9 @@ public void json_set_get_conditional_set() {
         assertEquals(
                 OK, Json.set(client, key, "$", jsonValue, ConditionalChange.ONLY_IF_DOES_NOT_EXIST).get());
         assertNull(Json.set(client, key, "$.a", "4.5", ConditionalChange.ONLY_IF_DOES_NOT_EXIST).get());
-        assertEquals("1.0", Json.get(client, key, ".a").get());
+        assertEquals("1.0", Json.get(client, key, new String[] {".a"}).get());
         assertEquals(OK, Json.set(client, key, "$.a", "4.5", ConditionalChange.ONLY_IF_EXISTS).get());
-        assertEquals("4.5", Json.get(client, key, ".a").get());
+        assertEquals("4.5", Json.get(client, key, new String[] {".a"}).get());
     }
 
     @Test
@@ -138,7 +138,7 @@ public void json_set_get_formatting() {
                 Json.get(
                                 client,
                                 key,
-                                "$",
+                                new String[] {"$"},
                                 JsonGetOptions.builder().indent("  ").newline("\n").space(" ").build())
                         .get();
         assertEquals(expectedGetResult, actualGetResult);
@@ -149,7 +149,7 @@ public void json_set_get_formatting() {
                 Json.get(
                                 client,
                                 key,
-                                "$",
+                                new String[] {"$"},
                                 JsonGetOptions.builder().indent("~").newline("\n").space("*").build())
                         .get();
         assertEquals(expectedGetResult2, actualGetResult2);

From 710ed04075973704c1c774f62525a4634ec183b4 Mon Sep 17 00:00:00 2001
From: James Xin 
Date: Thu, 17 Oct 2024 14:08:19 -0700
Subject: [PATCH 7/7] address comment 3

Signed-off-by: James Xin 
---
 .../java/glide/api/commands/servermodules/Json.java  | 12 ++++++++----
 1 file changed, 8 insertions(+), 4 deletions(-)

diff --git a/java/client/src/main/java/glide/api/commands/servermodules/Json.java b/java/client/src/main/java/glide/api/commands/servermodules/Json.java
index 2b24ce4faa..5aeb9fd851 100644
--- a/java/client/src/main/java/glide/api/commands/servermodules/Json.java
+++ b/java/client/src/main/java/glide/api/commands/servermodules/Json.java
@@ -140,7 +140,7 @@ public static CompletableFuture set(
      * @param client The Valkey GLIDE client to execute the command.
      * @param key The key of the JSON document.
      * @return Returns a string representation of the JSON document. If key doesn't
-     *     exist, returns null.
+     *     exist, returns null.
      * @example
      *     
{@code
      * String value = client.Json.get(client, "doc").get();
@@ -157,7 +157,7 @@ public static CompletableFuture get(@NonNull BaseClient client, @NonNull
      * @param client The Valkey GLIDE client to execute the command.
      * @param key The key of the JSON document.
      * @return Returns a string representation of the JSON document. If key doesn't
-     *     exist, returns null.
+     *     exist, returns null.
      * @example
      *     
{@code
      * GlideString value = client.Json.get(client, gs("doc")).get();
@@ -195,6 +195,8 @@ public static CompletableFuture get(
      *     path, the command behaves as if all are JSONPath paths.
      * @example
      *     
{@code
+     * String value = client.Json.get(client, "doc", new String[] {"$"}).get();
+     * assert value.equals("{'a': 1.0, 'b': 2}");
      * String value = client.Json.get(client, "doc", new String[] {"$.a", "$.b"}).get();
      * assert value.equals("{\"$.a\": [1.0], \"$.b\": [2]}");
      * }
@@ -231,6 +233,8 @@ public static CompletableFuture get( * path, the command behaves as if all are JSONPath paths. * @example *
{@code
+     * GlideString value = client.Json.get(client, gs("doc"), new GlideString[] {gs("$")}).get();
+     * assert value.equals(gs("{'a': 1.0, 'b': 2}"));
      * GlideString value = client.Json.get(client, gs("doc"), new GlideString[] {gs("$.a"), gs("$.b")}).get();
      * assert value.equals(gs("{\"$.a\": [1.0], \"$.b\": [2]}"));
      * }
@@ -250,7 +254,7 @@ public static CompletableFuture get( * @param options Options for formatting the byte representation of the JSON data. See * JsonGetOptions. * @return Returns a string representation of the JSON document. If key doesn't - * exist, returns null. + * exist, returns null. * @example *
{@code
      * JsonGetOptions options = JsonGetOptions.builder()
@@ -277,7 +281,7 @@ public static CompletableFuture get(
      * @param options Options for formatting the byte representation of the JSON data. See 
      *     JsonGetOptions.
      * @return Returns a string representation of the JSON document. If key doesn't
-     *     exist, returns null.
+     *     exist, returns null.
      * @example
      *     
{@code
      * JsonGetOptions options = JsonGetOptions.builder()