diff --git a/packages/client/lib/commands/generic-transformers.ts b/packages/client/lib/commands/generic-transformers.ts index badc580c12..82479e6c1f 100644 --- a/packages/client/lib/commands/generic-transformers.ts +++ b/packages/client/lib/commands/generic-transformers.ts @@ -753,11 +753,13 @@ export function transformRedisJsonArgument(json: RedisJSON): string { return JSON.stringify(json); } -export function transformRedisJsonReply(json: BlobStringReply): RedisJSON { - const res = JSON.parse((json as unknown as UnwrapReply).toString()); +export type JsonReviver = Parameters[1]; + +export function transformRedisJsonReply(json: BlobStringReply, reviver?: JsonReviver): RedisJSON { + const res = JSON.parse((json as unknown as UnwrapReply).toString(), reviver); return res; } -export function transformRedisJsonNullReply(json: NullReply | BlobStringReply): NullReply | RedisJSON { - return isNullReply(json) ? json : transformRedisJsonReply(json); +export function transformRedisJsonNullReply(json: NullReply | BlobStringReply, reviver?: JsonReviver ): NullReply | RedisJSON { + return isNullReply(json) ? json : transformRedisJsonReply(json, reviver); } diff --git a/packages/json/lib/commands/ARRPOP.spec.ts b/packages/json/lib/commands/ARRPOP.spec.ts index f823e7fc08..d4e7f122ec 100644 --- a/packages/json/lib/commands/ARRPOP.spec.ts +++ b/packages/json/lib/commands/ARRPOP.spec.ts @@ -63,5 +63,17 @@ describe('JSON.ARRPOP', () => { assert.deepEqual(reply, ['value']); }, GLOBAL.SERVERS.OPEN); + + testUtils.testWithClient('$ path with reviver', async client => { + const [, res] = await Promise.all([ + client.json.set('key', '$', [{ name: 'Alice', birthday: new Date('1998-02-12') }]), + client.json.arrPop('key', { + path: '$', + reviver: (key, value) => { if (key === 'birthday') return new Date(value); else return value; } + }) + ]); + + assert(typeof res === 'object' && res !== null && 'birthday' in res && res.birthday instanceof Date && res.birthday.getTime() === new Date('1998-02-12').getTime()); + }, GLOBAL.SERVERS.OPEN); }); }); diff --git a/packages/json/lib/commands/ARRPOP.ts b/packages/json/lib/commands/ARRPOP.ts index fdbae4fc2d..2d35d50504 100644 --- a/packages/json/lib/commands/ARRPOP.ts +++ b/packages/json/lib/commands/ARRPOP.ts @@ -1,11 +1,13 @@ import { CommandParser } from '@redis/client/dist/lib/client/parser'; import { RedisArgument, ArrayReply, NullReply, BlobStringReply, Command, UnwrapReply } from '@redis/client/dist/lib/RESP/types'; -import { isArrayReply, transformRedisJsonNullReply } from '@redis/client/dist/lib/commands/generic-transformers'; +import { isArrayReply, transformRedisJsonNullReply, JsonReviver } from '@redis/client/dist/lib/commands/generic-transformers'; -export interface RedisArrPopOptions { - path: RedisArgument; - index?: number; -} +export type RedisArrPopOptions = { + reviver?: JsonReviver; +} & ( + | { path?: never; index?: never } + | { path: RedisArgument; index?: number } +); export default { IS_READ_ONLY: false, @@ -14,17 +16,21 @@ export default { parser.pushKey(key); if (options) { - parser.push(options.path); + if (options.path !== undefined) { + parser.push(options.path); - if (options.index !== undefined) { - parser.push(options.index.toString()); + if (options.index !== undefined) { + parser.push(options.index.toString()); + } } + + parser.preserve = options.reviver; } }, - transformReply(reply: NullReply | BlobStringReply | ArrayReply) { + transformReply(reply: NullReply | BlobStringReply | ArrayReply, reviver?: JsonReviver) { return isArrayReply(reply) ? - (reply as unknown as UnwrapReply).map(item => transformRedisJsonNullReply(item)) : - transformRedisJsonNullReply(reply); + (reply as unknown as UnwrapReply).map(item => transformRedisJsonNullReply(item, reviver)) : + transformRedisJsonNullReply(reply, reviver); } } as const satisfies Command; diff --git a/packages/json/lib/commands/GET.spec.ts b/packages/json/lib/commands/GET.spec.ts index 0674fa5271..1941b7f878 100644 --- a/packages/json/lib/commands/GET.spec.ts +++ b/packages/json/lib/commands/GET.spec.ts @@ -42,6 +42,30 @@ describe('JSON.GET', () => { }, GLOBAL.SERVERS.OPEN); + testUtils.testWithClient('client.json.get with reviver', async client => { + assert.equal( + await client.json.get('key',{ reviver: ()=>{ assert.fail() } }), + null + ); + + await client.json.set('noderedis:users:1', '$', { name: 'Alice', birthday: new Date('1998-02-12') }); + const res = await client.json.get('noderedis:users:1', { reviver: (key, value) => { if (key === 'birthday') return new Date(value); else return value; } }); + assert(typeof res === 'object' && res !== null && 'birthday' in res && res.birthday instanceof Date && res.birthday.getTime() === new Date('1998-02-12').getTime()); + + }, GLOBAL.SERVERS.OPEN); + + testUtils.testWithClient('client.multi().json.get with reviver', async client => { + assert.equal( + (await client.multi().json.get('key',{ reviver: ()=>{ assert.fail() } }).exec())[0], + null + ); + + await client.json.set('noderedis:users:1', '$', { name: 'Alice', birthday: new Date('1998-02-12') }); + const res = (await client.multi().json.get('noderedis:users:1', { reviver: (key, value) => { if (key === 'birthday') return new Date(value); else return value; } }).exec())[0]; + assert(typeof res === 'object' && res !== null && 'birthday' in res && res.birthday instanceof Date && res.birthday.getTime() === new Date('1998-02-12').getTime()); + + }, GLOBAL.SERVERS.OPEN); + testUtils.testWithClient('client.json.get with path', async client => { await client.json.set('json:path:test', '$', { user: { name: 'Bob', age: 25 }, diff --git a/packages/json/lib/commands/GET.ts b/packages/json/lib/commands/GET.ts index 87d906543e..81d512be23 100644 --- a/packages/json/lib/commands/GET.ts +++ b/packages/json/lib/commands/GET.ts @@ -1,9 +1,10 @@ import { CommandParser } from '@redis/client/dist/lib/client/parser'; import { RedisArgument, Command } from '@redis/client/dist/lib/RESP/types'; -import { RedisVariadicArgument, transformRedisJsonNullReply } from '@redis/client/dist/lib/commands/generic-transformers'; +import { RedisVariadicArgument, transformRedisJsonNullReply, JsonReviver } from '@redis/client/dist/lib/commands/generic-transformers'; export interface JsonGetOptions { path?: RedisVariadicArgument; + reviver?: JsonReviver } export default { @@ -18,6 +19,7 @@ export default { if (options?.path !== undefined) { parser.pushVariadic(options.path); } + parser.preserve = options?.reviver; }, transformReply: transformRedisJsonNullReply } as const satisfies Command; \ No newline at end of file diff --git a/packages/json/lib/commands/MGET.spec.ts b/packages/json/lib/commands/MGET.spec.ts index 2d8efafde7..eaa3584972 100644 --- a/packages/json/lib/commands/MGET.spec.ts +++ b/packages/json/lib/commands/MGET.spec.ts @@ -17,4 +17,13 @@ describe('JSON.MGET', () => { [null, null] ); }, GLOBAL.SERVERS.OPEN); + + testUtils.testWithClient('client.json.mGet with reviver', async client => { + await client.json.set('noderedis:users:1', '$', { name: 'Alice', birthday: new Date('1998-02-12') }); + await client.json.set('noderedis:users:2', '$', { name: 'Bob', birthday: new Date('1996-07-23') }); + const res = await client.json.mGet(['noderedis:users:1', 'noderedis:users:2'], '.', (key, value) => { if (key === 'birthday') return new Date(value); else return value; }); + assert(typeof res[0] === 'object' && res[0] !== null && 'birthday' in res[0] && (res[0].birthday instanceof Date) && res[0].birthday.getTime() === new Date('1998-02-12').getTime()); + assert(typeof res[1] === 'object' && res[1] !== null && 'birthday' in res[1] && res[1].birthday instanceof Date && res[1].birthday.getTime() === new Date('1996-07-23').getTime()); + + }, GLOBAL.SERVERS.OPEN); }); diff --git a/packages/json/lib/commands/MGET.ts b/packages/json/lib/commands/MGET.ts index ec92468805..a8b26513a9 100644 --- a/packages/json/lib/commands/MGET.ts +++ b/packages/json/lib/commands/MGET.ts @@ -1,15 +1,31 @@ import { CommandParser } from '@redis/client/dist/lib/client/parser'; -import { RedisArgument, UnwrapReply, ArrayReply, NullReply, BlobStringReply, Command } from '@redis/client/dist/lib/RESP/types'; -import { transformRedisJsonNullReply } from '@redis/client/dist/lib/commands/generic-transformers'; +import { + RedisArgument, + UnwrapReply, + ArrayReply, + NullReply, + BlobStringReply, + Command, +} from '@redis/client/dist/lib/RESP/types'; +import { + transformRedisJsonNullReply, + JsonReviver, +} from '@redis/client/dist/lib/commands/generic-transformers'; export default { - IS_READ_ONLY: true, - parseCommand(parser: CommandParser, keys: Array, path: RedisArgument) { - parser.push('JSON.MGET'); - parser.pushKeys(keys); - parser.push(path); - }, - transformReply(reply: UnwrapReply>) { - return reply.map(json => transformRedisJsonNullReply(json)) - } + IS_READ_ONLY: true, + parseCommand( + parser: CommandParser, + keys: Array, + path: RedisArgument, + reviver?: JsonReviver, + ) { + parser.push('JSON.MGET'); + parser.pushKeys(keys); + parser.push(path); + parser.preserve = reviver; + }, + transformReply(reply: UnwrapReply>, reviver?: JsonReviver) { + return reply.map((json) => transformRedisJsonNullReply(json, reviver)); + }, } as const satisfies Command; diff --git a/packages/json/lib/commands/index.ts b/packages/json/lib/commands/index.ts index 774706dc5c..4337b80434 100644 --- a/packages/json/lib/commands/index.ts +++ b/packages/json/lib/commands/index.ts @@ -120,6 +120,7 @@ export default { * @param options - Optional parameters * @param options.path - Path to the array in the JSON document * @param options.index - Optional index to pop from. Default is -1 (last element) + * @param options.reviver - An optional reviver function to call when parsing the reply from Redis */ ARRPOP, /** @@ -130,6 +131,7 @@ export default { * @param options - Optional parameters * @param options.path - Path to the array in the JSON document * @param options.index - Optional index to pop from. Default is -1 (last element) + * @param options.reviver - An optional reviver function to call when parsing the reply from Redis */ arrPop: ARRPOP, /** @@ -231,6 +233,7 @@ export default { * @param key - The key containing the JSON document * @param options - Optional parameters * @param options.path - Path(s) to the value(s) to retrieve + * @param options.reviver - An optional reviver function to call when parsing the reply from Redis */ GET, /** @@ -240,6 +243,7 @@ export default { * @param key - The key containing the JSON document * @param options - Optional parameters * @param options.path - Path(s) to the value(s) to retrieve + * @param options.reviver - An optional reviver function to call when parsing the reply from Redis */ get: GET, /** @@ -266,6 +270,7 @@ export default { * * @param keys - Array of keys containing JSON documents * @param path - Path to retrieve from each document + * @param reviver - An optional reviver function to call when parsing the reply from Redis */ MGET, /** @@ -274,6 +279,7 @@ export default { * * @param keys - Array of keys containing JSON documents * @param path - Path to retrieve from each document + * @param reviver - An optional reviver function to call when parsing the reply from Redis */ mGet: MGET, /**