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

Python: add JSON.TYPE command #2409

Merged
merged 9 commits into from
Oct 9, 2024
42 changes: 42 additions & 0 deletions python/python/glide/async_commands/server_modules/json.py
Original file line number Diff line number Diff line change
Expand Up @@ -253,3 +253,45 @@ async def toggle(
TJsonResponse[bool],
await client.custom_command(["JSON.TOGGLE", key, path]),
)


async def type(
client: TGlideClient,
key: TEncodable,
path: Optional[TEncodable] = None,
) -> Optional[Union[bytes, List[bytes]]]:
"""
Retrieves the type of the JSON value at the specified `path` within the JSON document stored at `key`.

Args:
client (TGlideClient): The Redis client to execute the command.
key (TEncodable): The key of the JSON document.
path (Optional[TEncodable]): Represents the path within the JSON document where the type will be retrieved.
Defaults to None.

Returns:
Optional[Union[bytes, List[bytes]]]:
For JSONPath ('path' starts with '$'):
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.
For legacy path (`path` doesn't starts with `$`):
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, None will be returned.
If `key` doesn't exist, None is returned.

Examples:
>>> from glide import json
>>> await json.set(client, "doc", "$", '{"a": 1, "nested": {"a": 2, "b": 3}}')
>>> await json.type(client, "doc", "$.nested")
[b'object'] # Indicates the type of the value at path '$.nested' in the key stored at `doc`.
>>> await json.type(client, "doc", "$.nested.a")
[b'integer'] # Indicates the type of the value at path '$.nested.a' in the key stored at `doc`.
>>> await json.type(client, "doc", "$[*]")
[b'integer', b'object'] # Array of types in all top level elements.
"""
args = ["JSON.TYPE", key]
if path:
args.append(path)

return cast(Optional[Union[bytes, List[bytes]]], await client.custom_command(args))
73 changes: 73 additions & 0 deletions python/python/tests/tests_server_modules/test_json.py
Original file line number Diff line number Diff line change
Expand Up @@ -196,3 +196,76 @@ async def test_json_toggle(self, glide_client: TGlideClient):

with pytest.raises(RequestError):
assert await json.toggle(glide_client, "non_exiting_key", "$")

@pytest.mark.parametrize("cluster_mode", [True, False])
@pytest.mark.parametrize("protocol", [ProtocolVersion.RESP2, ProtocolVersion.RESP3])
async def test_json_type(self, glide_client: TGlideClient):
key = get_random_string(10)

json_value = {
"key1": "value1",
"key2": 2,
"key3": [1, 2, 3],
"key4": {"nested_key": {"key1": [4, 5]}},
"key5": None,
"key6": True,
}
assert await json.set(glide_client, key, "$", OuterJson.dumps(json_value)) == OK

result = await json.type(glide_client, key, "$")
assert result == [b"object"]

result = await json.type(glide_client, key, "$..key1")
assert result == [b"string", b"array"]

result = await json.type(glide_client, key, "$.key2")
assert result == [b"integer"]

result = await json.type(glide_client, key, "$.key3")
assert result == [b"array"]

result = await json.type(glide_client, key, "$.key4")
assert result == [b"object"]

result = await json.type(glide_client, key, "$.key4.nested_key")
assert result == [b"object"]

result = await json.type(glide_client, key, "$.key5")
assert result == [b"null"]

result = await json.type(glide_client, key, "$.key6")
assert result == [b"boolean"]

# Check for non-existent path in enhanced mode $.key7
result = await json.type(glide_client, key, "$.key7")
assert result == []

# Check for non-existent path within an existing key (array bound)
result = await json.type(
glide_client, key, "$.key3[3]"
) # Out of bounds for the array
assert result == []

# Legacy path (without $) - will return None for non-existing path
result = await json.type(glide_client, key, "key7")
assert result is None # Legacy path returns None for non-existent key

# Check for multiple path match in legacy
result = await json.type(glide_client, key, "..key1")
assert result == b"string"

# Check for non-existent key with enhanced path
result = await json.type(glide_client, "non_existent_key", "$.key1")
assert result is None

# Check for non-existent key with legacy path
result = await json.type(glide_client, "non_existent_key", "key1")
assert result is None # Returns None for legacy path when the key doesn't exist

# Check for all types in the JSON document using JSON Path
result = await json.type(glide_client, key, "$[*]")
assert result == [b"string", b"integer", b"array", b"object", b"null", b"boolean"]

# Check for all types in the JSON document using legacy path
result = await json.type(glide_client, key, "[*]")
assert result == b"string" # Expecting only the first type (string for key1)
Loading