Skip to content

Commit

Permalink
Node: Add command JSON.RESP (#2517)
Browse files Browse the repository at this point in the history
Node: Add command JSON.RESP

Signed-off-by: TJ Zhang <tj.zhang@improving.com>
  • Loading branch information
tjzhang-BQ authored Oct 28, 2024
1 parent a882b1f commit 01308db
Show file tree
Hide file tree
Showing 3 changed files with 178 additions and 0 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
* 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))
* Node: Added `FT.DROPINDEX` ([#2516](https://github.com/valkey-io/valkey-glide/pull/2516))
* Node: Added `JSON.RESP` ([#2517](https://github.com/valkey-io/valkey-glide/pull/2517))
* 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
48 changes: 48 additions & 0 deletions node/src/server-modules/GlideJson.ts
Original file line number Diff line number Diff line change
Expand Up @@ -352,4 +352,52 @@ export class GlideJson {

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

/**
* Retrieve the JSON value at the specified `path` within the JSON document stored at `key`.
* The returning result is in the Valkey or Redis OSS Serialization Protocol (RESP).
* JSON null is mapped to the RESP Null Bulk String.
* JSON Booleans are mapped to RESP Simple string.
* JSON integers are mapped to RESP Integers.
* JSON doubles are mapped to RESP Bulk Strings.
* JSON strings are mapped to RESP Bulk Strings.
* JSON arrays are represented as RESP arrays, where the first element is the simple string [, followed by the array's elements.
* JSON objects are represented as RESP object, where the first element is the simple string {, followed by key-value pairs, each of which is a RESP bulk string.
*
* @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:
* - For JSONPath (path starts with `$`):
* - Returns an array of replies for every possible path, indicating the RESP form of the JSON value.
* If `path` doesn't exist, returns an empty array.
* - For legacy path (path doesn't start with `$`):
* - Returns a single reply for the JSON value at the specified `path`, in its RESP form.
* If multiple paths match, the value of the first JSON value match is returned. 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, 2, 3], b: {a: [1, 2], c: {a: 42}}}"));
* // Output: 'OK' - Indicates successful setting of the value at path '.' in the key stored at `doc`.
* const result = await GlideJson.resp(client, "doc", "$..a");
* console.log(result);
* // Output: [ ["[", 1L, 2L, 3L], ["[", 1L, 2L], [42L]];
* console.log(await GlideJson.type(client, "doc", "..a")); // Output: ["[", 1L, 2L, 3L]
* ```
*/
static async resp(
client: BaseClient,
key: GlideString,
options?: { path: GlideString },
): Promise<ReturnTypeJson<GlideString>> {
const args = ["JSON.RESP", key];

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

return _executeCommand<ReturnTypeJson<GlideString>>(client, args);
}
}
129 changes: 129 additions & 0 deletions node/tests/ServerModules.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -590,6 +590,135 @@ describe("Server Module Tests", () => {
).toBeNull();
},
);

it.each([ProtocolVersion.RESP2, ProtocolVersion.RESP3])(
"json.resp tests",
async (protocol) => {
client = await GlideClusterClient.createClient(
getClientConfigurationOption(
cluster.getAddresses(),
protocol,
),
);
const key = uuidv4();
const jsonValue = {
obj: { a: 1, b: 2 },
arr: [1, 2, 3],
str: "foo",
bool: true,
int: 42,
float: 3.14,
nullVal: null,
};
// setup
expect(
await GlideJson.set(
client,
key,
"$",
JSON.stringify(jsonValue),
),
).toBe("OK");
expect(
await GlideJson.resp(client, key, { path: "$.*" }),
).toEqual([
["{", ["a", 1], ["b", 2]],
["[", 1, 2, 3],
"foo",
"true",
42,
"3.14",
null,
]); // leading "{" - JSON objects, leading "[" - JSON arrays

// multiple path match, the first will be returned
expect(
await GlideJson.resp(client, key, { path: "*" }),
).toEqual(["{", ["a", 1], ["b", 2]]);

// testing $ path
expect(
await GlideJson.resp(client, key, { path: "$" }),
).toEqual([
[
"{",
["obj", ["{", ["a", 1], ["b", 2]]],
["arr", ["[", 1, 2, 3]],
["str", "foo"],
["bool", "true"],
["int", 42],
["float", "3.14"],
["nullVal", null],
],
]);

// testing . path
expect(
await GlideJson.resp(client, key, { path: "." }),
).toEqual([
"{",
["obj", ["{", ["a", 1], ["b", 2]]],
["arr", ["[", 1, 2, 3]],
["str", "foo"],
["bool", "true"],
["int", 42],
["float", "3.14"],
["nullVal", null],
]);

// $.str and .str
expect(
await GlideJson.resp(client, key, { path: "$.str" }),
).toEqual(["foo"]);
expect(
await GlideJson.resp(client, key, { path: ".str" }),
).toEqual("foo");

// setup new json value
const jsonValue2 = {
a: [1, 2, 3],
b: { a: [1, 2], c: { a: 42 } },
};
expect(
await GlideJson.set(
client,
key,
"$",
JSON.stringify(jsonValue2),
),
).toBe("OK");

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

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

// error case
await expect(
GlideJson.resp(client, key, { path: "nonexistent" }),
).rejects.toThrow(RequestError);

// non-existent key
expect(
await GlideJson.resp(client, "nonexistent_key", {
path: "$",
}),
).toBeNull();
expect(
await GlideJson.resp(client, "nonexistent_key", {
path: ".",
}),
).toBeNull();
expect(
await GlideJson.resp(client, "nonexistent_key"),
).toBeNull();
},
);
});

describe("GlideFt", () => {
Expand Down

0 comments on commit 01308db

Please sign in to comment.