1 | /**
|
2 | Omit any index signatures from the given object type, leaving only explicitly defined properties.
|
3 |
|
4 | This is the counterpart of `PickIndexSignature`.
|
5 |
|
6 | Use-cases:
|
7 | - Remove overly permissive signatures from third-party types.
|
8 |
|
9 | This type was taken from this [StackOverflow answer](https://stackoverflow.com/a/68261113/420747).
|
10 |
|
11 | It 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 | ```
|
16 | const indexed: Record<string, unknown> = {}; // Allowed
|
17 |
|
18 | const keyed: Record<'foo', unknown> = {}; // Error
|
19 | // => TS2739: Type '{}' is missing the following properties from type 'Record<"foo" | "bar", unknown>': foo, bar
|
20 | ```
|
21 |
|
22 | Instead 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 | ```
|
25 | type 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 |
|
30 | type 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 |
|
36 | Using 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 | ```
|
39 | import type {OmitIndexSignature} from 'type-fest';
|
40 |
|
41 | type 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 | ```
|
50 | import type {OmitIndexSignature} from 'type-fest';
|
51 |
|
52 | type 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 |
|
62 | If `{}` 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 | ```
|
65 | import type {OmitIndexSignature} from 'type-fest';
|
66 |
|
67 | type 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 | ```
|
78 | import type {OmitIndexSignature} from 'type-fest';
|
79 |
|
80 | interface 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 |
|
96 | type ExampleWithoutIndexSignatures = OmitIndexSignature<Example>;
|
97 | // => { foo: 'bar'; qux?: 'baz' | undefined; }
|
98 | ```
|
99 |
|
100 | @see PickIndexSignature
|
101 | @category Object
|
102 | */
|
103 | export type OmitIndexSignature<ObjectType> = {
|
104 | [KeyType in keyof ObjectType as {} extends Record<KeyType, unknown>
|
105 | ? never
|
106 | : KeyType]: ObjectType[KeyType];
|
107 | };
|