Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Java: add JSON.RESP #2513

Open
wants to merge 4 commits into
base: release-1.2
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
* Node: Added `JSON.DEL` and `JSON.FORGET` ([#2505](https://github.com/valkey-io/valkey-glide/pull/2505))
* Java: Added `JSON.TOGGLE` ([#2504](https://github.com/valkey-io/valkey-glide/pull/2504))
* Node: Added `JSON.TYPE` ([#2510](https://github.com/valkey-io/valkey-glide/pull/2510))
* Java: Added `JSON.RESP` ([#2513](https://github.com/valkey-io/valkey-glide/pull/2513))

#### Breaking Changes

Expand Down
153 changes: 145 additions & 8 deletions java/client/src/main/java/glide/api/commands/servermodules/Json.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ public class Json {
private static final String JSON_DEL = JSON_PREFIX + "DEL";
private static final String JSON_FORGET = JSON_PREFIX + "FORGET";
private static final String JSON_TOGGLE = JSON_PREFIX + "TOGGLE";
private static final String JSON_RESP = JSON_PREFIX + "RESP";
private static final String JSON_TYPE = JSON_PREFIX + "TYPE";

private Json() {}

Expand Down Expand Up @@ -189,11 +191,12 @@ public static CompletableFuture<GlideString> get(
* <ul>
* <li>For JSONPath (path starts with <code>$</code>): Returns a stringified JSON list
* replies for every possible path, or a string representation of an empty array,
* if path doesn't exist. If <code>key</code> doesn't exist, returns None.
* if path doesn't exist. If <code>key</code> doesn't exist, returns <code>null
* </code>.
* <li>For legacy path (path doesn't start with <code>$</code>): Returns a string
* representation of the value in <code>paths</code>. If <code>paths</code>
* doesn't exist, an error is raised. If <code>key</code> doesn't exist, returns
* None.
* <code>null</code>.
* </ul>
* <li>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
Expand Down Expand Up @@ -226,11 +229,12 @@ public static CompletableFuture<String> get(
* <ul>
* <li>For JSONPath (path starts with <code>$</code>): Returns a stringified JSON list
* replies for every possible path, or a string representation of an empty array,
* if path doesn't exist. If <code>key</code> doesn't exist, returns None.
* if path doesn't exist. If <code>key</code> doesn't exist, returns <code>null
* </code>.
* <li>For legacy path (path doesn't start with <code>$</code>): Returns a string
* representation of the value in <code>paths</code>. If <code>paths</code>
* doesn't exist, an error is raised. If <code>key</code> doesn't exist, returns
* None.
* <code>null</code>.
* </ul>
* <li>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
Expand Down Expand Up @@ -317,11 +321,12 @@ public static CompletableFuture<GlideString> get(
* <ul>
* <li>For JSONPath (path starts with <code>$</code>): Returns a stringified JSON list
* replies for every possible path, or a string representation of an empty array,
* if path doesn't exist. If <code>key</code> doesn't exist, returns None.
* if path doesn't exist. If <code>key</code> doesn't exist, returns <code>null
* </code>.
* <li>For legacy path (path doesn't start with <code>$</code>): Returns a string
* representation of the value in <code>paths</code>. If <code>paths</code>
* doesn't exist, an error is raised. If <code>key</code> doesn't exist, returns
* None.
* <code>null</code>.
* </ul>
* <li>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
Expand Down Expand Up @@ -363,11 +368,12 @@ public static CompletableFuture<String> get(
* <ul>
* <li>For JSONPath (path starts with <code>$</code>): Returns a stringified JSON list
* replies for every possible path, or a string representation of an empty array,
* if path doesn't exist. If <code>key</code> doesn't exist, returns None.
* if path doesn't exist. If <code>key</code> doesn't exist, returns <code>null
* </code>.
* <li>For legacy path (path doesn't start with <code>$</code>): Returns a string
* representation of the value in <code>paths</code>. If <code>paths</code>
* doesn't exist, an error is raised. If <code>key</code> doesn't exist, returns
* None.
* <code>null</code>.
* </ul>
* <li>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
Expand Down Expand Up @@ -1209,6 +1215,137 @@ public static CompletableFuture<Object> toggle(
client, new ArgsBuilder().add(gs(JSON_TOGGLE)).add(key).add(path).toArray());
}

/**
* Retrieves the JSON document stored at <code>key</code>. The returning result is in the Valkey or Redis OSS Serialization Protocol (RESP).
*
* @param client The Valkey GLIDE client to execute the command.
* @param key The <code>key</code> of the JSON document.
* @return Returns the JSON document in its RESP form.
* If <code>key</code> doesn't exist, <code>null</code> is returned.
* @example
* <pre>{@code
* Json.set(client, "doc", ".", "{\"a\": [1, 2, 3], \"b\": {\"b1\": 1}, \"c\": 42}");
* Object actualResult = Json.resp(client, "doc").get();
* Object[] expectedResult = new Object[] {
* "{",
* "a",
* new Object[] {"[", 1L, 2L, 3L},
* "b",
* new Object[] {"{", "b1", 1L},
* "c",
* 42L
* };
* assertInstanceOf(Object[].class, actualResult);
* assertArrayEquals(expectedResult, (Object[]) actualResult);
* }</pre>
*/
public static CompletableFuture<Object> resp(@NonNull BaseClient client, @NonNull String key) {
return executeCommand(client, new String[] {JSON_RESP, key});
}

/**
* Retrieves the JSON document stored at <code>key</code>. The returning result is in the Valkey or Redis OSS Serialization Protocol (RESP).
*
* @param client The Valkey GLIDE client to execute the command.
* @param key The <code>key</code> of the JSON document.
* @return Returns the JSON document in its RESP form.
* If <code>key</code> doesn't exist, <code>null</code> is returned.
* @example
* <pre>{@code
* Json.set(client, "doc", ".", "{\"a\": [1, 2, 3], \"b\": {\"b1\": 1}, \"c\": 42}");
* Object actualResultBinary = Json.resp(client, gs("doc")).get();
* Object[] expectedResultBinary = new Object[] {
* gs("{"),
* gs("a"),
* new Object[] {gs("["), 1L, 2L, 3L},
* gs("b"),
* new Object[] {gs("{"), gs("b1"), 1L},
* gs("c"),
* 42L
* };
* assertInstanceOf(Object[].class, actualResultBinary);
* assertArrayEquals(expectedResultBinary, (Object[]) actualResultBinary);
* }</pre>
*/
public static CompletableFuture<Object> resp(
@NonNull BaseClient client, @NonNull GlideString key) {
return executeCommand(client, new GlideString[] {gs(JSON_RESP), key});
}

/**
* Retrieve the JSON value at the specified <code>path</code> within the JSON document stored at
* <code>key</code>. The returning result is in the Valkey or Redis OSS Serialization Protocol
* (RESP).
*
* @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
* <ul>
* <li>For JSONPath (<code>path</code> starts with <code>$</code>): Returns a list of
* replies for every possible path, indicating the RESP form of the JSON value. If
* <code>path</code> doesn't exist, returns an empty list.
* <li>For legacy path (<code>path</code> doesn't starts with <code>$</code>): Returns a
* single reply for the JSON value at the specified path, in its RESP form. If multiple
* paths match, the value of the first JSON value match is returned. If <code>path
* </code> doesn't exist, an error is raised.
* </ul>
* If <code>key</code> doesn't exist, <code>null</code> is returned.
* @example
* <pre>{@code
* Json.set(client, "doc", ".", "{\"a\": [1, 2, 3], \"b\": {\"a\": [1, 2], \"c\": {\"a\": 42}}}");
* Object actualResult = Json.resp(client, "doc", "$..a").get(); // JSONPath returns all possible paths
* Object[] expectedResult = new Object[] {
* new Object[] {"[", 1L, 2L, 3L},
* new Object[] {"[", 1L, 2L},
* 42L}
* assertArrayEquals(expectedResult, (Object[]) actualResult);
* // legacy path only returns the first JSON value match
* assertArrayEquals(new Object[] {"[", 1L, 2L, 3L}, (Object[]) Json.resp(client, key, "..a").get());
* }</pre>
*/
public static CompletableFuture<Object> resp(
@NonNull BaseClient client, @NonNull String key, @NonNull String path) {
return executeCommand(client, new String[] {JSON_RESP, key, path});
}

/**
* Retrieve the JSON value at the specified <code>path</code> within the JSON document stored at
* <code>key</code>. The returning result is in the Valkey or Redis OSS Serialization Protocol
* (RESP).
*
* @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
* <ul>
* <li>For JSONPath (<code>path</code> starts with <code>$</code>): Returns a list of
* replies for every possible path, indicating the RESP form of the JSON value. If
* <code>path</code> doesn't exist, returns an empty list.
* <li>For legacy path (<code>path</code> doesn't starts with <code>$</code>): Returns a
* single reply for the JSON value at the specified path, in its RESP form. If multiple
* paths match, the value of the first JSON value match is returned. If <code>path
* </code> doesn't exist, an error is raised.
* </ul>
* If <code>key</code> doesn't exist, <code>null</code> is returned.
* @example
* <pre>{@code
* Json.set(client, "doc", ".", "{\"a\": [1, 2, 3], \"b\": {\"a\": [1, 2], \"c\": {\"a\": 42}}}");
* Object actualResult = Json.resp(client, gs("doc"), gs("$..a")).get(); // JSONPath returns all possible paths
* Object[] expectedResult = new Object[] {
* new Object[] {gs("["), 1L, 2L, 3L},
* new Object[] {gs("["), 1L, 2L},
* 42L}
* assertArrayEquals(expectedResult, (Object[]) actualResult);
* // legacy path only returns the first JSON value match
* assertArrayEquals(new Object[] {gs("["), 1L, 2L, 3L}, (Object[]) Json.resp(client, gs(key), gs("..a")).get());
* }</pre>
*/
public static CompletableFuture<Object> resp(
@NonNull BaseClient client, @NonNull GlideString key, @NonNull GlideString path) {
return executeCommand(client, new GlideString[] {gs(JSON_RESP), key, path});
}

/**
* A wrapper for custom command API.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -516,4 +516,86 @@ void forget_binary_with_path_returns_success() {
assertEquals(expectedResponse, actualResponse);
assertEquals(expectedResponseValue, actualResponseValue);
}

@Test
@SneakyThrows
void resp_without_path_returns_success() {
// setup
String key = "testKey";
CompletableFuture<Object> expectedResponse = new CompletableFuture<>();
String expectedResponseValue = "foo";
expectedResponse.complete(expectedResponseValue);
when(glideClient.customCommand(eq(new String[] {"JSON.RESP", key})).thenApply(any()))
.thenReturn(expectedResponse);

// exercise
CompletableFuture<Object> actualResponse = Json.resp(glideClient, key);
Object actualResponseValue = actualResponse.get();

// verify
assertEquals(expectedResponse, actualResponse);
assertEquals(expectedResponseValue, actualResponseValue);
}

@Test
@SneakyThrows
void resp_binary_without_path_returns_success() {
// setup
GlideString key = gs("testKey");
CompletableFuture<Object> expectedResponse = new CompletableFuture<>();
GlideString expectedResponseValue = gs("foo");
expectedResponse.complete(expectedResponseValue);
when(glideClient.customCommand(eq(new GlideString[] {gs("JSON.RESP"), key})).thenApply(any()))
.thenReturn(expectedResponse);

// exercise
CompletableFuture<Object> actualResponse = Json.resp(glideClient, key);
Object actualResponseValue = actualResponse.get();

// verify
assertEquals(expectedResponse, actualResponse);
assertEquals(expectedResponseValue, actualResponseValue);
}

@Test
@SneakyThrows
void resp_with_path_returns_success() {
// setup
String key = "testKey";
CompletableFuture<Object> expectedResponse = new CompletableFuture<>();
String expectedResponseValue = "foo";
expectedResponse.complete(expectedResponseValue);
when(glideClient.customCommand(eq(new String[] {"JSON.RESP", key, "$"})).thenApply(any()))
.thenReturn(expectedResponse);

// exercise
CompletableFuture<Object> actualResponse = Json.resp(glideClient, key, "$");
Object actualResponseValue = actualResponse.get();

// verify
assertEquals(expectedResponse, actualResponse);
assertEquals(expectedResponseValue, actualResponseValue);
}

@Test
@SneakyThrows
void resp_binary_with_path_returns_success() {
// setup
GlideString key = gs("testKey");
CompletableFuture<Object> expectedResponse = new CompletableFuture<>();
GlideString expectedResponseValue = gs("foo");
expectedResponse.complete(expectedResponseValue);
when(glideClient
.customCommand(eq(new GlideString[] {gs("JSON.RESP"), key, gs("$")}))
.thenApply(any()))
.thenReturn(expectedResponse);

// exercise
CompletableFuture<Object> actualResponse = Json.resp(glideClient, key, gs("$"));
Object actualResponseValue = actualResponse.get();

// verify
assertEquals(expectedResponse, actualResponse);
assertEquals(expectedResponseValue, actualResponseValue);
}
}
Loading
Loading