Skip to content

Commit 0d36c8f

Browse files
committed
Improve objectEntries types
1 parent 456198a commit 0d36c8f

File tree

2 files changed

+24
-3
lines changed

2 files changed

+24
-3
lines changed

src/helpers/object_helpers.test.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { describe, expect, it, vi } from 'vitest';
1+
import { describe, expect, expectTypeOf, it, vi } from 'vitest';
22
import { tt } from '@noeldemartin/testing';
33
import type { Expect } from '@noeldemartin/testing';
44

@@ -9,6 +9,7 @@ import {
99
monkeyPatch,
1010
objectDeepClone,
1111
objectDeepValue,
12+
objectEntries,
1213
objectOnly,
1314
objectWithout,
1415
objectWithoutEmpty,
@@ -138,6 +139,18 @@ const originalWithoutEmpty = objectWithoutEmpty(original);
138139

139140
describe('Object helpers types', () => {
140141

142+
it('gets typed entries', () => {
143+
const partialObject = {} as Partial<Record<'a' | 'b', number>>;
144+
const objectWithUndefined = {} as Record<'a' | 'b', number | undefined>;
145+
const objectWithOptional = {} as { a?: number; b: number };
146+
const objectWithOptionalAndRequired = {} as { a?: number; b: number | undefined };
147+
148+
expectTypeOf(objectEntries(partialObject)).toEqualTypeOf<['a' | 'b', number][]>();
149+
expectTypeOf(objectEntries(objectWithUndefined)).toEqualTypeOf<['a' | 'b', number | undefined][]>();
150+
expectTypeOf(objectEntries(objectWithOptional)).toEqualTypeOf<['a' | 'b', number][]>();
151+
expectTypeOf(objectEntries(objectWithOptionalAndRequired)).toEqualTypeOf<['a' | 'b', number | undefined][]>();
152+
});
153+
141154
it(
142155
'has correct types',
143156
tt<

src/helpers/object_helpers.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import type {
22
Closure,
33
ClosureArgs,
44
Equals,
5+
GetClosureArgs,
56
GetObjectMethods,
67
KeyOf,
78
Pretty,
@@ -12,7 +13,12 @@ import type { Constructor } from '@noeldemartin/utils/types/classes';
1213
import type { DeepKeyOf, DeepValue } from '@noeldemartin/utils/types';
1314

1415
export type Obj = Record<string, unknown>;
15-
export type ObjectEntry<T extends Obj, K extends keyof T> = [K, T[K]];
16+
17+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
18+
export type GetObjectKeys<T> = T extends Record<infer Key, any> ? Key : keyof T;
19+
20+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
21+
export type GetObjectValues<T> = T extends Record<any, infer Value> ? Value : never;
1622

1723
export type ValueWithout<TValue, TExclude> = TValue extends TExclude ? never : TValue;
1824
export type ReplaceValues<TObj, TExclude> = { [K in keyof TObj]: ValueWithout<TObj[K], TExclude> };
@@ -169,7 +175,9 @@ export function objectDeepValue<T extends object, K extends DeepKeyOf<T>>(object
169175
return value as DeepValue<T, K>;
170176
}
171177

172-
export const objectEntries = Object.entries.bind(Object) as <T extends Obj>(obj: T) => ObjectEntry<T, keyof T>[];
178+
export const objectEntries = Object.entries.bind(Object) as <T extends GetClosureArgs<typeof Object.entries>[0]>(
179+
obj: T
180+
) => [GetObjectKeys<T>, GetObjectValues<T>][];
173181

174182
export function objectHasOwnProperty(object: Obj, property: string): boolean {
175183
return Object.prototype.hasOwnProperty.call(object, property);

0 commit comments

Comments
 (0)