UNPKG

3.74 kBTypeScriptView Raw
1/**
2Omit any index signatures from the given object type, leaving only explicitly defined properties.
3
4This is the counterpart of `PickIndexSignature`.
5
6Use-cases:
7- Remove overly permissive signatures from third-party types.
8
9This type was taken from this [StackOverflow answer](https://stackoverflow.com/a/68261113/420747).
10
11It relies on the fact that an empty object (`{}`) is assignable to an object with just an index signature, like `Record<string, unknown>`, but not to an object with explicitly defined keys, like `Record<'foo' | 'bar', unknown>`.
12
13(The actual value type, `unknown`, is irrelevant and could be any type. Only the key type matters.)
14
15```
16const indexed: Record<string, unknown> = {}; // Allowed
17
18const keyed: Record<'foo', unknown> = {}; // Error
19// => TS2739: Type '{}' is missing the following properties from type 'Record<"foo" | "bar", unknown>': foo, bar
20```
21
22Instead of causing a type error like the above, you can also use a [conditional type](https://www.typescriptlang.org/docs/handbook/2/conditional-types.html) to test whether a type is assignable to another:
23
24```
25type Indexed = {} extends Record<string, unknown>
26 ? '✅ `{}` is assignable to `Record<string, unknown>`'
27 : '❌ `{}` is NOT assignable to `Record<string, unknown>`';
28// => '✅ `{}` is assignable to `Record<string, unknown>`'
29
30type Keyed = {} extends Record<'foo' | 'bar', unknown>
31 ? "✅ `{}` is assignable to `Record<'foo' | 'bar', unknown>`"
32 : "❌ `{}` is NOT assignable to `Record<'foo' | 'bar', unknown>`";
33// => "❌ `{}` is NOT assignable to `Record<'foo' | 'bar', unknown>`"
34```
35
36Using a [mapped type](https://www.typescriptlang.org/docs/handbook/2/mapped-types.html#further-exploration), you can then check for each `KeyType` of `ObjectType`...
37
38```
39import type {OmitIndexSignature} from 'type-fest';
40
41type OmitIndexSignature<ObjectType> = {
42 [KeyType in keyof ObjectType // Map each key of `ObjectType`...
43 ]: ObjectType[KeyType]; // ...to its original value, i.e. `OmitIndexSignature<Foo> == Foo`.
44};
45```
46
47...whether an empty object (`{}`) would be assignable to an object with that `KeyType` (`Record<KeyType, unknown>`)...
48
49```
50import type {OmitIndexSignature} from 'type-fest';
51
52type OmitIndexSignature<ObjectType> = {
53 [KeyType in keyof ObjectType
54 // Is `{}` assignable to `Record<KeyType, unknown>`?
55 as {} extends Record<KeyType, unknown>
56 ? ... // ✅ `{}` is assignable to `Record<KeyType, unknown>`
57 : ... // ❌ `{}` is NOT assignable to `Record<KeyType, unknown>`
58 ]: ObjectType[KeyType];
59};
60```
61
62If `{}` is assignable, it means that `KeyType` is an index signature and we want to remove it. If it is not assignable, `KeyType` is a "real" key and we want to keep it.
63
64```
65import type {OmitIndexSignature} from 'type-fest';
66
67type OmitIndexSignature<ObjectType> = {
68 [KeyType in keyof ObjectType
69 as {} extends Record<KeyType, unknown>
70 ? never // => Remove this `KeyType`.
71 : KeyType // => Keep this `KeyType` as it is.
72 ]: ObjectType[KeyType];
73};
74```
75
76@example
77```
78import type {OmitIndexSignature} from 'type-fest';
79
80interface Example {
81 // These index signatures will be removed.
82 [x: string]: any
83 [x: number]: any
84 [x: symbol]: any
85 [x: `head-${string}`]: string
86 [x: `${string}-tail`]: string
87 [x: `head-${string}-tail`]: string
88 [x: `${bigint}`]: string
89 [x: `embedded-${number}`]: string
90
91 // These explicitly defined keys will remain.
92 foo: 'bar';
93 qux?: 'baz';
94}
95
96type ExampleWithoutIndexSignatures = OmitIndexSignature<Example>;
97// => { foo: 'bar'; qux?: 'baz' | undefined; }
98```
99
100@see PickIndexSignature
101@category Object
102*/
103export type OmitIndexSignature<ObjectType> = {
104 [KeyType in keyof ObjectType as {} extends Record<KeyType, unknown>
105 ? never
106 : KeyType]: ObjectType[KeyType];
107};