From dc11d294e99ddecdccaa41cc30d0de8aec31a094 Mon Sep 17 00:00:00 2001 From: Gao Sun Date: Fri, 23 Dec 2022 18:29:09 +0800 Subject: [PATCH] feat: get() --- src/utilities/get.test.ts | 19 ++++++++++++++ src/utilities/get.ts | 52 +++++++++++++++++++++++++++++++++++++++ src/utilities/index.ts | 1 + 3 files changed, 72 insertions(+) create mode 100644 src/utilities/get.test.ts create mode 100644 src/utilities/get.ts diff --git a/src/utilities/get.test.ts b/src/utilities/get.test.ts new file mode 100644 index 0000000..7390c3e --- /dev/null +++ b/src/utilities/get.test.ts @@ -0,0 +1,19 @@ +import { get, getValue } from './get.js'; + +describe('get()', () => { + const object = { aaa: 'asdasd', bbb: { ccc: 123, ddd: { eee: [true] } } }; + + it('should return proper value with even nested keys', () => { + expect(get(object, 'aaa')).toStrictEqual('asdasd'); + expect(get(object, 'bbb')).toStrictEqual({ ccc: 123, ddd: { eee: [true] } }); + expect(get(object, 'bbb.ccc')).toStrictEqual(123); + expect(getValue(object, 'bbb.ddd')).toStrictEqual({ eee: [true] }); + expect(getValue(object, 'bbb.ddd.eee')).toStrictEqual([true]); + }); + + it('should return undefined of throw TypeError when accessing non-existing value', () => { + // eslint-disable-next-line unicorn/no-useless-undefined + expect(get(object, 'ddd')).toStrictEqual(undefined); + expect(() => get(object, 'ddd.ccc')).toThrowError(TypeError); + }); +}); diff --git a/src/utilities/get.ts b/src/utilities/get.ts new file mode 100644 index 0000000..5089b51 --- /dev/null +++ b/src/utilities/get.ts @@ -0,0 +1,52 @@ +type ValidKeys = keyof T extends string ? keyof T : never; + +export type KeySerial> = + | ValidKeys + | { + [key in ValidKeys]: T[key] extends unknown[] + ? never + : T[key] extends Record + ? `${key}.${KeySerial}` + : never; + }[ValidKeys]; + +export type ExtractKeySerialType< + T extends Record, + Serial extends string +> = Serial extends `${infer Key}.${infer Rest}` + ? T[Key] extends Record + ? ExtractKeySerialType + : never + : T[Serial]; + +/* eslint-disable @silverhand/fp/no-let, @silverhand/fp/no-mutation */ +type Get = { + , Serial extends KeySerial>( + object: T, + keySerial: Serial + ): ExtractKeySerialType; + // eslint-disable-next-line @typescript-eslint/ban-types + (object: T, keySerial: unknown): unknown; +}; + +export const get: Get = , Serial extends KeySerial>( + object: T, + keySerial: Serial +): ExtractKeySerialType => { + let result: unknown = object; + + for (const key of keySerial.split('.')) { + // @ts-expect-error for performance + result = result[key]; + } + + // eslint-disable-next-line no-restricted-syntax + return result as ExtractKeySerialType; +}; + +/** Same to `get()` but with the strict type definition only to enable IntelliSense. */ +export const getValue = , Serial extends KeySerial>( + object: T, + keySerial: Serial +): ExtractKeySerialType => get(object, keySerial); +/* eslint-enable @silverhand/fp/no-let, @silverhand/fp/no-mutation */ diff --git a/src/utilities/index.ts b/src/utilities/index.ts index fe606c8..2766cc5 100644 --- a/src/utilities/index.ts +++ b/src/utilities/index.ts @@ -7,3 +7,4 @@ export * from './function.js'; export * from './pick.js'; export * from './string.js'; export * from './types.js'; +export * from './get.js';