Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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 @@ -18,6 +18,7 @@
* Core: Update routing for commands from server modules ([#2461](https://github.com/valkey-io/valkey-glide/pull/2461))
* Node: Added `JSON.SET` and `JSON.GET` ([#2427](https://github.com/valkey-io/valkey-glide/pull/2427))
* Java: Added `JSON.ARRAPPEND` ([#2489](https://github.com/valkey-io/valkey-glide/pull/2489))
* Node: Added `JSON.TOGGLE` ([#2491](https://github.com/valkey-io/valkey-glide/pull/2491))

#### Breaking Changes

Expand Down
61 changes: 56 additions & 5 deletions node/src/server-modules/GlideJson.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { ConditionalChange } from "../Commands";
import { GlideClient } from "../GlideClient";
import { GlideClusterClient, RouteOption } from "../GlideClusterClient";

export type ReturnTypeJson = GlideString | (GlideString | null)[];
export type ReturnTypeJson<T> = T | (T | null)[];

/**
* Represents options for formatting JSON data, to be used in the [JSON.GET](https://valkey.io/commands/json.get/) command.
Expand Down Expand Up @@ -80,6 +80,7 @@ export class GlideJson {
/**
* Sets the JSON value at the specified `path` stored at `key`.
*
* @param client The 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.
Expand Down Expand Up @@ -123,8 +124,11 @@ export class GlideJson {
/**
* Retrieves the JSON value at the specified `paths` stored at `key`.
*
* @param client The client to execute the command.
* @param key - The key of the JSON document.
* @param options - Options for formatting the byte representation of the JSON data. See {@link JsonGetOptions}.
* @param options - (Optional) Additional parameters:
* - (Optional) Options for formatting the byte representation of the JSON data. See {@link JsonGetOptions}.
* - (Optional) `decoder`: see {@link DecoderOption}.
* @returns ReturnTypeJson:
* - If one path is given:
* - For JSONPath (path starts with `$`):
Expand Down Expand Up @@ -164,17 +168,64 @@ export class GlideJson {
* ```
*/
static async get(
client: GlideClient | GlideClusterClient,
client: BaseClient,
key: GlideString,
options?: JsonGetOptions & DecoderOption,
): Promise<ReturnTypeJson> {
): Promise<ReturnTypeJson<GlideString>> {
const args = ["JSON.GET", key];

if (options) {
const optionArgs = _jsonGetOptionsToArgs(options);
args.push(...optionArgs);
}

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

/**
* Toggles a Boolean value 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 path - The JSONPath to specify.
Comment thread
tjzhang-BQ marked this conversation as resolved.
Outdated
* @param option - see {@link DecoderOption}.
Comment thread
tjzhang-BQ marked this conversation as resolved.
Outdated
* @returns - For JSONPath (`path` starts with `$`), returns a list of boolean replies for every possible path, with the toggled boolean value,
* or null for JSON values matching the path that are not boolean.
* - For legacy path (`path` doesn't starts with `$`), returns the value of the toggled boolean in `path`.
* - Note that when sending legacy path syntax, If `path` doesn't exist or the value at `path` isn't a boolean, an error is raised.
*
* @example
* ```typescript
* const value = {bool: true, nested: {bool: false, nested: {bool: 10}}};
* const jsonStr = JSON.stringify(value);
* const resultSet = await GlideJson.set("doc", "$", jsonStr);
* // Output: 'OK'
*
* const resultToggle = await.GlideJson.toggle(client, "doc", "$.bool")
* // Output: [false, true, null] - Indicates successful toggling of the Boolean values at path '$.bool' in the key stored at `doc`.
*
* const resultToggle = await.GlideJson.toggle(client, "doc", "bool")
* // Output: true - Indicates successful toggling of the Boolean value at path 'bool' in the key stored at `doc`.
*
* const resultToggle = await.GlideJson.toggle(client, "doc", "bool")
* // Output: true - Indicates successful toggling of the Boolean value at path 'bool' in the key stored at `doc`.
*
* const jsonGetStr = await GlideJson.get(client, "doc", "$");
* console.log(JSON.stringify(jsonGetStr));
* // Output: [{bool: true, nested: {bool: true, nested: {bool: 10}}}] - The updated JSON value in the key stored at `doc`.
* ```
*/
static async toggle(
client: BaseClient,
key: GlideString,
path: GlideString,
option?: DecoderOption,
): Promise<ReturnTypeJson<boolean>> {
const args = ["JSON.TOGGLE", key, path];
return _executeCommand<ReturnTypeJson<boolean>>(client, args, option);
}
}
46 changes: 46 additions & 0 deletions node/tests/ServerModules.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
InfoOptions,
JsonGetOptions,
ProtocolVersion,
RequestError,
} from "..";
import { ValkeyCluster } from "../../utils/TestUtils";
import {
Expand Down Expand Up @@ -227,4 +228,49 @@ describe("GlideJson", () => {
expect(result).toEqual(expectedResult2);
},
);

it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])(
"json.toggle tests",
async (protocol) => {
client = await GlideClusterClient.createClient(
getClientConfigurationOption(cluster.getAddresses(), protocol),
);
const key = uuidv4();
const jsonValue = {
bool: true,
nested: { bool: false, nested: { bool: 10 } },
};
expect(
await GlideJson.set(
client,
key,
"$",
JSON.stringify(jsonValue),
),
).toBe("OK");
expect(await GlideJson.toggle(client, key, "$..bool")).toEqual([
false,
true,
null,
]);
expect(await GlideJson.toggle(client, key, "bool")).toBe(true);
expect(
await GlideJson.toggle(client, key, "$.non_existing"),
).toEqual([]);
expect(await GlideJson.toggle(client, key, "$.nested")).toEqual([
null,
]);

// expect request errors
await expect(
GlideJson.toggle(client, key, "nested"),
).rejects.toThrow(RequestError);
await expect(
GlideJson.toggle(client, key, ".non_existing"),
).rejects.toThrow(RequestError);
await expect(
GlideJson.toggle(client, "non_existing_key", "$"),
).rejects.toThrow(RequestError);
},
);
});