Skip to content

Commit f65b0fe

Browse files
authored
Node: Add command JSON.TOGGLE (#2491)
* Node: Add command JSON.TOGGLE Signed-off-by: TJ Zhang <tj.zhang@improving.com> Co-authored-by: TJ Zhang <tj.zhang@improving.com>
1 parent f977b91 commit f65b0fe

File tree

3 files changed

+118
-5
lines changed

3 files changed

+118
-5
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
* Core: Update routing for commands from server modules ([#2461](https://github.com/valkey-io/valkey-glide/pull/2461))
2121
* Node: Added `JSON.SET` and `JSON.GET` ([#2427](https://github.com/valkey-io/valkey-glide/pull/2427))
2222
* Java: Added `JSON.ARRAPPEND` ([#2489](https://github.com/valkey-io/valkey-glide/pull/2489))
23+
* Node: Added `JSON.TOGGLE` ([#2491](https://github.com/valkey-io/valkey-glide/pull/2491))
2324

2425
#### Breaking Changes
2526

node/src/server-modules/GlideJson.ts

Lines changed: 65 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { ConditionalChange } from "../Commands";
77
import { GlideClient } from "../GlideClient";
88
import { GlideClusterClient, RouteOption } from "../GlideClusterClient";
99

10-
export type ReturnTypeJson = GlideString | (GlideString | null)[];
10+
export type ReturnTypeJson<T> = T | (T | null)[];
1111

1212
/**
1313
* Represents options for formatting JSON data, to be used in the [JSON.GET](https://valkey.io/commands/json.get/) command.
@@ -80,6 +80,7 @@ export class GlideJson {
8080
/**
8181
* Sets the JSON value at the specified `path` stored at `key`.
8282
*
83+
* @param client The client to execute the command.
8384
* @param key - The key of the JSON document.
8485
* @param path - Represents the path within the JSON document where the value will be set.
8586
* 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.
@@ -123,8 +124,11 @@ export class GlideJson {
123124
/**
124125
* Retrieves the JSON value at the specified `paths` stored at `key`.
125126
*
127+
* @param client The client to execute the command.
126128
* @param key - The key of the JSON document.
127-
* @param options - Options for formatting the byte representation of the JSON data. See {@link JsonGetOptions}.
129+
* @param options - (Optional) Additional parameters:
130+
* - (Optional) Options for formatting the byte representation of the JSON data. See {@link JsonGetOptions}.
131+
* - (Optional) `decoder`: see {@link DecoderOption}.
128132
* @returns ReturnTypeJson:
129133
* - If one path is given:
130134
* - For JSONPath (path starts with `$`):
@@ -164,17 +168,73 @@ export class GlideJson {
164168
* ```
165169
*/
166170
static async get(
167-
client: GlideClient | GlideClusterClient,
171+
client: BaseClient,
168172
key: GlideString,
169173
options?: JsonGetOptions & DecoderOption,
170-
): Promise<ReturnTypeJson> {
174+
): Promise<ReturnTypeJson<GlideString>> {
171175
const args = ["JSON.GET", key];
172176

173177
if (options) {
174178
const optionArgs = _jsonGetOptionsToArgs(options);
175179
args.push(...optionArgs);
176180
}
177181

178-
return _executeCommand<ReturnTypeJson>(client, args, options);
182+
return _executeCommand<ReturnTypeJson<GlideString>>(
183+
client,
184+
args,
185+
options,
186+
);
187+
}
188+
189+
/**
190+
* Toggles a Boolean value stored at the specified `path` within the JSON document stored at `key`.
191+
*
192+
* @param client - The client to execute the command.
193+
* @param key - The key of the JSON document.
194+
* @param options - (Optional) Additional parameters:
195+
* - (Optional) The JSONPath to specify. Defaults to the root if not specified.
196+
* @returns - For JSONPath (`path` starts with `$`), returns a list of boolean replies for every possible path, with the toggled boolean value,
197+
* or null for JSON values matching the path that are not boolean.
198+
* - For legacy path (`path` doesn't starts with `$`), returns the value of the toggled boolean in `path`.
199+
* - 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.
200+
*
201+
* @example
202+
* ```typescript
203+
* const value = {bool: true, nested: {bool: false, nested: {bool: 10}}};
204+
* const jsonStr = JSON.stringify(value);
205+
* const resultSet = await GlideJson.set("doc", "$", jsonStr);
206+
* // Output: 'OK'
207+
*
208+
* const resultToggle = await.GlideJson.toggle(client, "doc", "$.bool")
209+
* // Output: [false, true, null] - Indicates successful toggling of the Boolean values at path '$.bool' in the key stored at `doc`.
210+
*
211+
* const resultToggle = await.GlideJson.toggle(client, "doc", "bool")
212+
* // Output: true - Indicates successful toggling of the Boolean value at path 'bool' in the key stored at `doc`.
213+
*
214+
* const resultToggle = await.GlideJson.toggle(client, "doc", "bool")
215+
* // Output: true - Indicates successful toggling of the Boolean value at path 'bool' in the key stored at `doc`.
216+
*
217+
* const jsonGetStr = await GlideJson.get(client, "doc", "$");
218+
* console.log(JSON.stringify(jsonGetStr));
219+
* // Output: [{bool: true, nested: {bool: true, nested: {bool: 10}}}] - The updated JSON value in the key stored at `doc`.
220+
*
221+
* // Without specifying a path, the path defaults to root.
222+
* console.log(await GlideJson.set(client, "doc2", ".", true)); // Output: "OK"
223+
* console.log(await GlideJson.toggle(client,"doc2")); // Output: "false"
224+
* console.log(await GlideJson.toggle(client, "doc2")); // Output: "true"
225+
* ```
226+
*/
227+
static async toggle(
228+
client: BaseClient,
229+
key: GlideString,
230+
options?: { path: GlideString },
231+
): Promise<ReturnTypeJson<boolean>> {
232+
const args = ["JSON.TOGGLE", key];
233+
234+
if (options !== undefined) {
235+
args.push(options.path);
236+
}
237+
238+
return _executeCommand<ReturnTypeJson<boolean>>(client, args);
179239
}
180240
}

node/tests/ServerModules.test.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {
1717
InfoOptions,
1818
JsonGetOptions,
1919
ProtocolVersion,
20+
RequestError,
2021
} from "..";
2122
import { ValkeyCluster } from "../../utils/TestUtils";
2223
import {
@@ -227,4 +228,55 @@ describe("GlideJson", () => {
227228
expect(result).toEqual(expectedResult2);
228229
},
229230
);
231+
232+
it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])(
233+
"json.toggle tests",
234+
async (protocol) => {
235+
client = await GlideClusterClient.createClient(
236+
getClientConfigurationOption(cluster.getAddresses(), protocol),
237+
);
238+
const key = uuidv4();
239+
const key2 = uuidv4();
240+
const jsonValue = {
241+
bool: true,
242+
nested: { bool: false, nested: { bool: 10 } },
243+
};
244+
expect(
245+
await GlideJson.set(
246+
client,
247+
key,
248+
"$",
249+
JSON.stringify(jsonValue),
250+
),
251+
).toBe("OK");
252+
expect(
253+
await GlideJson.toggle(client, key, { path: "$..bool" }),
254+
).toEqual([false, true, null]);
255+
expect(await GlideJson.toggle(client, key, { path: "bool" })).toBe(
256+
true,
257+
);
258+
expect(
259+
await GlideJson.toggle(client, key, { path: "$.non_existing" }),
260+
).toEqual([]);
261+
expect(
262+
await GlideJson.toggle(client, key, { path: "$.nested" }),
263+
).toEqual([null]);
264+
265+
// testing behavior with default pathing
266+
expect(await GlideJson.set(client, key2, ".", "true")).toBe("OK");
267+
expect(await GlideJson.toggle(client, key2)).toBe(false);
268+
expect(await GlideJson.toggle(client, key2)).toBe(true);
269+
270+
// expect request errors
271+
await expect(
272+
GlideJson.toggle(client, key, { path: "nested" }),
273+
).rejects.toThrow(RequestError);
274+
await expect(
275+
GlideJson.toggle(client, key, { path: ".non_existing" }),
276+
).rejects.toThrow(RequestError);
277+
await expect(
278+
GlideJson.toggle(client, "non_existing_key", { path: "$" }),
279+
).rejects.toThrow(RequestError);
280+
},
281+
);
230282
});

0 commit comments

Comments
 (0)