-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
3 changed files
with
72 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 */ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters