Skip to content
Merged
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 @@ -56,6 +56,7 @@
* Node: Added `JSON.CLEAR` ([#2566](https://github.com/valkey-io/valkey-glide/pull/2566))
* Node: Added `JSON.ARRTRIM` ([#2550](https://github.com/valkey-io/valkey-glide/pull/2550))
* Node: Added `JSON.ARRINDEX` ([#2559](https://github.com/valkey-io/valkey-glide/pull/2559))
* Node: Added `JSON.OBJLEN` and `JSON.OBJKEYS` ([#2563](https://github.com/valkey-io/valkey-glide/pull/2563))
* Python: Add `JSON.STRAPPEND` , `JSON.STRLEN` commands ([#2372](https://github.com/valkey-io/valkey-glide/pull/2372))
* Python: Add `JSON.OBJKEYS` command ([#2395](https://github.com/valkey-io/valkey-glide/pull/2395))
* Python: Add `JSON.ARRINSERT` command ([#2464](https://github.com/valkey-io/valkey-glide/pull/2464))
Expand Down
85 changes: 85 additions & 0 deletions node/src/server-modules/GlideJson.ts
Original file line number Diff line number Diff line change
Expand Up @@ -988,4 +988,89 @@ export class GlideJson {
const args = ["JSON.NUMMULTBY", key, path, num.toString()];
return _executeCommand(client, args);
}

/**
* Retrieves the number of key-value pairs in the object stored at the specified `path` within the JSON document stored at `key`.
*
* @param client - The client to execute the command.
* @param key - The key of the JSON document.
* @param options - (Optional) Additional parameters:
* - (Optional) `path`: The path within the JSON document, Defaults to root (`"."`) if not provided.
* @returns ReturnTypeJson<number>:
* - For JSONPath (`path` starts with `$`):
* - Returns a list of integer replies for every possible path, indicating the length of the object,
* or `null` for JSON values matching the path that are not an object.
* - If `path` doesn't exist, an empty array will be returned.
* - For legacy path (`path` doesn't starts with `$`):
* - Returns the length of the object at `path`.
* - If multiple paths match, the length of the first object match is returned.
* - If the JSON value at `path` is not an object or if `path` doesn't exist, an error is raised.
* - If `key` doesn't exist, `null` is returned.
*
* @example
* ```typescript
* console.log(await GlideJson.set(client, "doc", "$", '{"a": 1.0, "b": {"a": {"x": 1, "y": 2}, "b": 2.5, "c": true}}'));
* // Output: 'OK' - Indicates successful setting of the value at the root path '$' in the key `doc`.
* console.log(await GlideJson.objlen(client, "doc", { path: "$" }));
* // Output: [2] - Returns the number of key-value pairs at the root object, which has 2 keys: 'a' and 'b'.
* console.log(await GlideJson.objlen(client, "doc", { path: "." }));
* // Output: 2 - Returns the number of key-value pairs for the object matching the path '.', which has 2 keys: 'a' and 'b'.
* ```
*/
static async objlen(
client: BaseClient,
key: GlideString,
options?: { path: GlideString },
): Promise<ReturnTypeJson<number>> {
const args = ["JSON.OBJLEN", key];

if (options) {
args.push(options.path);
}

return _executeCommand<ReturnTypeJson<number>>(client, args);
}

/**
* Retrieves key names in the object values at the specified `path` within the JSON document stored at `key`.
*
* @param client - The client to execute the command.
* @param key - The key of the JSON document.
* @param options - (Optional) Additional parameters:
* - (Optional) `path`: The path within the JSON document where the key names will be retrieved. Defaults to root (`"."`) if not provided.
* @returns ReturnTypeJson<GlideString[]>:
* - For JSONPath (`path` starts with `$`):
* - Returns a list of arrays containing key names for each matching object.
* - If a value matching the path is not an object, an empty array is returned.
* - If `path` doesn't exist, an empty array is returned.
* - For legacy path (`path` starts with `.`):
* - Returns a list of key names for the object value matching the path.
* - If multiple objects match the path, the key names of the first object is returned.
* - If a value matching the path is not an object, an error is raised.
* - If `path` doesn't exist, `null` is returned.
* - If `key` doesn't exist, `null` is returned.
*
* @example
* ```typescript
* console.log(await GlideJson.set(client, "doc", "$", '{"a": 1.0, "b": {"a": {"x": 1, "y": 2}, "b": 2.5, "c": true}}'));
* // Output: 'OK' - Indicates successful setting of the value at the root path '$' in the key `doc`.
* console.log(await GlideJson.objkeys(client, "doc", { path: "$" }));
* // Output: [["a", "b"]] - Returns a list of arrays containing the key names for objects matching the path '$'.
* console.log(await GlideJson.objkeys(client, "doc", { path: "." }));
* // Output: ["a", "b"] - Returns key names for the object matching the path '.' as it is the only match.
* ```
*/
static async objkeys(
client: BaseClient,
key: GlideString,
options?: { path: GlideString } & DecoderOption,
): Promise<ReturnTypeJson<GlideString[]>> {
const args = ["JSON.OBJKEYS", key];

if (options) {
args.push(options.path);
}

return _executeCommand(client, args, options);
}
}
201 changes: 201 additions & 0 deletions node/tests/ServerModules.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2038,6 +2038,207 @@ describe("Server Module Tests", () => {
).toEqual(19);
},
);

it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])(
"json.objlen tests",
async (protocol) => {
client = await GlideClusterClient.createClient(
getClientConfigurationOption(
cluster.getAddresses(),
protocol,
),
);
const key = uuidv4();
const jsonValue = {
a: 1.0,
b: { a: { x: 1, y: 2 }, b: 2.5, c: true },
};

// setup
expect(
await GlideJson.set(
client,
key,
"$",
JSON.stringify(jsonValue),
),
).toBe("OK");

expect(
await GlideJson.objlen(client, key, { path: "$" }),
).toEqual([2]);

expect(
await GlideJson.objlen(client, key, { path: "." }),
).toEqual(2);

expect(
await GlideJson.objlen(client, key, { path: "$.." }),
).toEqual([2, 3, 2]);

expect(
await GlideJson.objlen(client, key, { path: ".." }),
).toEqual(2);

expect(
await GlideJson.objlen(client, key, { path: "$..b" }),
).toEqual([3, null]);

expect(
await GlideJson.objlen(client, key, { path: "..b" }),
).toEqual(3);

expect(
await GlideJson.objlen(client, Buffer.from(key), {
path: Buffer.from("..a"),
}),
).toEqual(2);

expect(await GlideJson.objlen(client, key)).toEqual(2);

// path doesn't exist
expect(
await GlideJson.objlen(client, key, {
path: "$.non_existing_path",
}),
).toEqual([]);

await expect(
GlideJson.objlen(client, key, {
path: "non_existing_path",
}),
).rejects.toThrow(RequestError);

// Value at path isnt an object
expect(
await GlideJson.objlen(client, key, {
path: "$.non_existing_path",
}),
).toEqual([]);

await expect(
GlideJson.objlen(client, key, { path: ".a" }),
).rejects.toThrow(RequestError);

// Non-existing key
expect(
await GlideJson.objlen(client, "non_existing_key", {
path: "$",
}),
).toBeNull();

expect(
await GlideJson.objlen(client, "non_existing_key", {
path: ".",
}),
).toBeNull();

expect(
await GlideJson.set(
client,
key,
"$",
'{"a": 1, "b": 2, "c":3, "d":4}',
),
).toBe("OK");
expect(await GlideJson.objlen(client, key)).toEqual(4);
},
);

it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])(
"json.objkeys tests",
async (protocol) => {
client = await GlideClusterClient.createClient(
getClientConfigurationOption(
cluster.getAddresses(),
protocol,
),
);
const key = uuidv4();
const jsonValue = {
a: 1.0,
b: { a: { x: 1, y: 2 }, b: 2.5, c: true },
};

// setup
expect(
await GlideJson.set(
client,
key,
"$",
JSON.stringify(jsonValue),
),
).toBe("OK");

expect(
await GlideJson.objkeys(client, key, { path: "$" }),
).toEqual([["a", "b"]]);

expect(
await GlideJson.objkeys(client, key, {
path: ".",
decoder: Decoder.Bytes,
}),
).toEqual([Buffer.from("a"), Buffer.from("b")]);

expect(
await GlideJson.objkeys(client, Buffer.from(key), {
path: Buffer.from("$.."),
}),
).toEqual([
["a", "b"],
["a", "b", "c"],
["x", "y"],
]);

expect(
await GlideJson.objkeys(client, key, { path: ".." }),
).toEqual(["a", "b"]);

expect(
await GlideJson.objkeys(client, key, { path: "$..b" }),
).toEqual([["a", "b", "c"], []]);

expect(
await GlideJson.objkeys(client, key, { path: "..b" }),
).toEqual(["a", "b", "c"]);

// path doesn't exist
expect(
await GlideJson.objkeys(client, key, {
path: "$.non_existing_path",
}),
).toEqual([]);

expect(
await GlideJson.objkeys(client, key, {
path: "non_existing_path",
}),
).toBeNull();

// Value at path isnt an object
expect(
await GlideJson.objkeys(client, key, { path: "$.a" }),
).toEqual([[]]);

await expect(
GlideJson.objkeys(client, key, { path: ".a" }),
).rejects.toThrow(RequestError);

// Non-existing key
expect(
await GlideJson.objkeys(client, "non_existing_key", {
path: "$",
}),
).toBeNull();

expect(
await GlideJson.objkeys(client, "non_existing_key", {
path: ".",
}),
).toBeNull();
},
);
},
);

Expand Down