diff --git a/.changeset/smooth-news-deliver.md b/.changeset/smooth-news-deliver.md new file mode 100644 index 0000000..2de1ec0 --- /dev/null +++ b/.changeset/smooth-news-deliver.md @@ -0,0 +1,5 @@ +--- +'emery': patch +--- + +Add `typedObjectFromEntries` utility, and improve `ObjectEntry` type. diff --git a/src/index.ts b/src/index.ts index ac584b6..3ad49da 100644 --- a/src/index.ts +++ b/src/index.ts @@ -34,7 +34,7 @@ export { export { castToOpaque } from './opaques'; export { getErrorMessage } from './utils/error'; -export { typedEntries, typedKeys } from './utils/object'; +export { typedEntries, typedKeys, typedObjectFromEntries } from './utils/object'; // Types // ------------------------------ diff --git a/src/types.ts b/src/types.ts index f405b5f..a11e51c 100644 --- a/src/types.ts +++ b/src/types.ts @@ -5,7 +5,7 @@ export type ErrorLike = { message: string }; -export type ObjectEntry = { [K in keyof T]: [K, T[K]] }[keyof T]; +export type ObjectEntry = { [K in keyof T]-?: [K, T[K]] }[keyof T]; export type Nullish = null | undefined; diff --git a/src/utils/object.test.ts b/src/utils/object.test.ts index ce8ce73..aeeced4 100644 --- a/src/utils/object.test.ts +++ b/src/utils/object.test.ts @@ -1,4 +1,4 @@ -import { typedEntries, typedKeys } from './object'; +import { typedEntries, typedKeys, typedObjectFromEntries } from './object'; describe('utils/object', () => { describe('typedEntries', () => { @@ -16,4 +16,13 @@ describe('utils/object', () => { expect(result).toEqual(['foo', 'bar']); }); }); + describe('typedObjectFromEntries', () => { + it('should return the correct object', () => { + const result = typedObjectFromEntries([ + ['foo', 1], + ['bar', 2], + ]); + expect(result).toEqual({ foo: 1, bar: 2 }); + }); + }); }); diff --git a/src/utils/object.ts b/src/utils/object.ts index 966a81a..42a3d07 100644 --- a/src/utils/object.ts +++ b/src/utils/object.ts @@ -21,3 +21,18 @@ export function typedEntries(value: T) { export function typedKeys(value: T) { return Object.keys(value) as Array; } + +/** + * An alternative to `Object.fromEntries()` that avoids type widening. Must be + * used in conjunction with `typedEntries` or `typedKeys`. + * + * @example + * const obj = { name: 'Alice', age: 30 }; + * const rebuilt1 = Object.fromEntries(Object.entries(obj)); + * // ^? { [k: string]: string | number } + * const rebuilt2 = typedObjectFromEntries(typedEntries(obj)); + * // ^? { name: string, age: number } + */ +export function typedObjectFromEntries(entries: ObjectEntry[]) { + return Object.fromEntries(entries) as T; +}