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.TYPE #2525

Open
wants to merge 2 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 @@ -31,6 +31,7 @@
* Java: Added `JSON.CLEAR` ([#2519](https://github.com/valkey-io/valkey-glide/pull/2519))
* 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))
* Java: Added `JSON.TYPE` ([#2525](https://github.com/valkey-io/valkey-glide/pull/2525))
* Node: Added `FT.DROPINDEX` ([#2516](https://github.com/valkey-io/valkey-glide/pull/2516))

#### Breaking Changes
Expand Down
108 changes: 108 additions & 0 deletions java/client/src/main/java/glide/api/commands/servermodules/Json.java
Original file line number Diff line number Diff line change
Expand Up @@ -1607,6 +1607,114 @@ public static CompletableFuture<Object> resp(
return executeCommand(client, new GlideString[] {gs(JSON_RESP), key, path});
}

/**
* Retrieves the type of the JSON value at the root of the JSON document stored at <code>key
* </code>.
*
* @param client The Valkey GLIDE client to execute the command.
* @param key The key of the JSON document.
* @return Returns the type of the JSON value at root. If <code>key</code> doesn't exist, <code>
* null</code> is returned.
* @example
* <pre>{@code
* Json.set(client, "doc", "$", "[1, 2, 3]");
* assertEquals("array", Json.type(client, "doc").get());
*
* Json.set(client, "doc", "$", "{\"a\": 1}");
* assertEquals("object", Json.type(client, "doc").get());
*
* assertNull(Json.type(client, "non_existing_key").get());
* }</pre>
*/
public static CompletableFuture<Object> type(@NonNull BaseClient client, @NonNull String key) {
return executeCommand(client, new String[] {JSON_TYPE, key});
}

/**
* Retrieves the type of the JSON value at the root of the JSON document stored at <code>key
* </code>.
*
* @param client The Valkey GLIDE client to execute the command.
* @param key The key of the JSON document.
* @return Returns the type of the JSON value at root. If <code>key</code> doesn't exist, <code>
* null</code> is returned.
* @example
* <pre>{@code
* Json.set(client, "doc", "$", "[1, 2, 3]");
* assertEquals(gs("array"), Json.type(client, gs("doc")).get());
*
* Json.set(client, "doc", "$", "{\"a\": 1}");
* assertEquals(gs("object"), Json.type(client, gs("doc")).get());
*
* assertNull(Json.type(client, gs("non_existing_key")).get());
* }</pre>
*/
public static CompletableFuture<Object> type(
@NonNull BaseClient client, @NonNull GlideString key) {
return executeCommand(client, new GlideString[] {gs(JSON_TYPE), key});
}

/**
* Retrieves the type of the JSON value at the specified <code>path</code> within the JSON
* document stored at <code>key</code>.
*
* @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 type will be retrieved.
* @return
* <ul>
* <li>For JSONPath (<code>path</code> starts with <code>$</code>): Returns a list of byte
* string replies for every possible path, indicating the type of the JSON value. If
* `path` doesn't exist, an empty array will be returned.
* <li>For legacy path (<code>path</code> doesn't starts with <code>$</code>): Returns the
* type of the JSON value at `path`. If multiple paths match, the type of the first JSON
* value match is returned. If `path` doesn't exist, <code>null</code> will be returned.
* </ul>
* If <code>key</code> doesn't exist, <code>null</code> is returned.
* @example
* <pre>{@code
* Json.set(client, "doc", "$", "{\"a\": 1, \"nested\": {\"a\": 2, \"b\": 3}}");
* assertArrayEquals(new Object[]{"object"}, (Object[]) Json.type(client, key, "$.nested").get());
* assertArrayEquals(new Object[]{"integer"}, (Object[]) Json.type(client, key, "$.nested.a").get());
* assertArrayEquals(new Object[]{"integer", "object"}, (Object[]) Json.type(client, key, "$[*]").get());
* }</pre>
*/
public static CompletableFuture<Object> type(
@NonNull BaseClient client, @NonNull String key, @NonNull String path) {

return executeCommand(client, new String[] {JSON_TYPE, key, path});
}

/**
* Retrieves the type of the JSON value at the specified <code>path</code> within the JSON
* document stored at <code>key</code>.
*
* @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 type will be retrieved.
* @return
* <ul>
* <li>For JSONPath (<code>path</code> starts with <code>$</code>): Returns a list of byte
* string replies for every possible path, indicating the type of the JSON value. If
* `path` doesn't exist, an empty array will be returned.
* <li>For legacy path (<code>path</code> doesn't starts with <code>$</code>): Returns the
* type of the JSON value at `path`. If multiple paths match, the type of the first JSON
* value match is returned. If `path` doesn't exist, <code>null</code> will be returned.
* </ul>
* If <code>key</code> doesn't exist, <code>null</code> is returned.
* @example
* <pre>{@code
* Json.set(client, "doc", "$", "{\"a\": 1, \"nested\": {\"a\": 2, \"b\": 3}}");
* assertArrayEquals(new Object[]{gs("object")}, (Object[]) Json.type(client, gs(key), gs("$.nested")).get());
* assertArrayEquals(new Object[]{gs("integer")}, (Object[]) Json.type(client, gs(key), gs("$.nested.a")).get());
* assertArrayEquals(new Object[]{gs("integer"), gs("object")}, (Object[]) Json.type(client, gs(key), gs("$[*]")).get());
* }</pre>
*/
public static CompletableFuture<Object> type(
@NonNull BaseClient client, @NonNull GlideString key, @NonNull GlideString path) {
return executeCommand(client, new GlideString[] {gs(JSON_TYPE), key, path});
}

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

@Test
@SneakyThrows
void type_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.TYPE", key})).thenApply(any()))
.thenReturn(expectedResponse);

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

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

@Test
@SneakyThrows
void type_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.TYPE"), key})).thenApply(any()))
.thenReturn(expectedResponse);

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

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

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

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

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

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

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

// verify
assertEquals(expectedResponse, actualResponse);
assertEquals(expectedResponseValue, actualResponseValue);
}
}
41 changes: 41 additions & 0 deletions java/integTest/src/test/java/glide/modules/JsonTests.java
Original file line number Diff line number Diff line change
Expand Up @@ -639,4 +639,45 @@ public void json_resp() {
assertNull(Json.resp(client, "nonexistent_key", ".").get());
assertNull(Json.resp(client, "nonexistent_key").get());
}

@Test
@SneakyThrows
public void json_type() {
String key = UUID.randomUUID().toString();
String jsonValue =
"{\"key1\": \"value1\", \"key2\": 2, \"key3\": [1, 2, 3], \"key4\": {\"nested_key\":"
+ " {\"key1\": [4, 5]}}, \"key5\": null, \"key6\": true}";
assertEquals(OK, Json.set(client, key, "$", jsonValue).get());

assertArrayEquals(new Object[] {"object"}, (Object[]) Json.type(client, key, "$").get());
assertArrayEquals(
new Object[] {gs("string"), gs("array")},
(Object[]) Json.type(client, gs(key), gs("$..key1")).get());
assertArrayEquals(new Object[] {"integer"}, (Object[]) Json.type(client, key, "$.key2").get());
assertArrayEquals(new Object[] {"array"}, (Object[]) Json.type(client, key, "$.key3").get());
assertArrayEquals(new Object[] {"object"}, (Object[]) Json.type(client, key, "$.key4").get());
assertArrayEquals(
new Object[] {"object"}, (Object[]) Json.type(client, key, "$.key4.nested_key").get());
assertArrayEquals(new Object[] {"null"}, (Object[]) Json.type(client, key, "$.key5").get());
assertArrayEquals(new Object[] {"boolean"}, (Object[]) Json.type(client, key, "$.key6").get());
// Check for non-existent path in enhanced mode $.key7
assertArrayEquals(new Object[] {}, (Object[]) Json.type(client, key, "$.key7").get());
// Check for non-existent path within an existing key (array bound)
assertArrayEquals(new Object[] {}, (Object[]) Json.type(client, key, "$.key3[3]").get());
// Legacy path (without $) - will return None for non-existing path
assertNull(Json.type(client, key, "key7").get());
// Check for multiple path match in legacy
assertEquals("string", Json.type(client, key, "..key1").get());
// Check for non-existent key with enhanced path
assertNull(Json.type(client, "non_existing_key", "$.key1").get());
// Check for non-existent key with legacy path
assertNull(Json.type(client, "non_existing_key", "key1").get());
// Check for all types in the JSON document using JSON Path
Object[] actualResult = (Object[]) Json.type(client, key, "$[*]").get();
Object[] expectedResult =
new Object[] {"string", "integer", "array", "object", "null", "boolean"};
assertArrayEquals(expectedResult, actualResult);
// Check for all types in the JSON document using legacy path
assertEquals("string", Json.type(client, key, "[*]").get());
}
}
Loading