Skip to content

Commit

Permalink
feat: get()
Browse files Browse the repository at this point in the history
  • Loading branch information
gao-sun committed Dec 23, 2022
1 parent 5ca0d2d commit dc11d29
Show file tree
Hide file tree
Showing 3 changed files with 72 additions and 0 deletions.
19 changes: 19 additions & 0 deletions src/utilities/get.test.ts
Original file line number Diff line number Diff line change
@@ -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);
});
});
52 changes: 52 additions & 0 deletions src/utilities/get.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
type ValidKeys<T> = keyof T extends string ? keyof T : never;

export type KeySerial<T extends Record<string, unknown>> =
| ValidKeys<T>
| {
[key in ValidKeys<T>]: T[key] extends unknown[]
? never
: T[key] extends Record<string, unknown>
? `${key}.${KeySerial<T[key]>}`
: never;
}[ValidKeys<T>];

export type ExtractKeySerialType<
T extends Record<string, unknown>,
Serial extends string
> = Serial extends `${infer Key}.${infer Rest}`
? T[Key] extends Record<string, unknown>
? ExtractKeySerialType<T[Key], Rest>
: never
: T[Serial];

/* eslint-disable @silverhand/fp/no-let, @silverhand/fp/no-mutation */
type Get = {
<T extends Record<string, unknown>, Serial extends KeySerial<T>>(
object: T,
keySerial: Serial
): ExtractKeySerialType<T, Serial>;
// eslint-disable-next-line @typescript-eslint/ban-types
<T extends object>(object: T, keySerial: unknown): unknown;
};

export const get: Get = <T extends Record<string, unknown>, Serial extends KeySerial<T>>(
object: T,
keySerial: Serial
): ExtractKeySerialType<T, Serial> => {
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<T, Serial>;
};

/** Same to `get()` but with the strict type definition only to enable IntelliSense. */
export const getValue = <T extends Record<string, unknown>, Serial extends KeySerial<T>>(
object: T,
keySerial: Serial
): ExtractKeySerialType<T, Serial> => get(object, keySerial);
/* eslint-enable @silverhand/fp/no-let, @silverhand/fp/no-mutation */
1 change: 1 addition & 0 deletions src/utilities/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ export * from './function.js';
export * from './pick.js';
export * from './string.js';
export * from './types.js';
export * from './get.js';

0 comments on commit dc11d29

Please sign in to comment.