'How to remove index signature using mapped types
Given an interface (from an existing .d.ts file that can't be changed):
interface Foo {
[key: string]: any;
bar(): void;
}
Is there a way to use mapped types (or another method) to derive a new type without the index signature? i.e. it only has the method bar(): void;
Solution 1:[1]
With TypeScript v4.1 key remapping leads to a very concise solution.
At its core it uses a slightly modified logic from Mihail's answer: while a known key is a subtype of either string or number, the latter are not subtypes of the corresponding literal. On the other hand, string is a union of all possible strings (the same holds true for number) and thus is reflexive (type res = string extends string ? true : false; //true holds).
This means you can resolve to never every time the type string or number is assignable to the type of key, effectively filtering it out:
interface Foo {
[key: string]: any;
[key: number]: any;
bar(): void;
}
type RemoveIndex<T> = {
[ P in keyof T as string extends P ? never : number extends P ? never : P ] : T[P]
};
type FooWithOnlyBar = RemoveIndex<Foo>; //{ bar: () => void; }
Solution 2:[2]
With TypeScript 4.4, the language gained support for more complex index signatures.
interface FancyIndices {
[x: symbol]: number;
[x: `data-${string}`]: string
}
The symbol key can be trivially caught by adding a case for it in the previously posted type, but this style of check cannot detect infinite template literals.1
However, we can achieve the same goal by modifying the check to see if an object constructed with each key is assignable to an empty object. This works because "real" keys will require that the object constructed with Record<K, 1> have a property, and will therefore not be assignable, while keys which are index signatures will result in a type which may contain only the empty object.
type RemoveIndex<T> = {
[K in keyof T as {} extends Record<K, 1> ? never : K]: T[K]
}
Try it out in the playground
Test:
class X {
[x: string]: any
[x: number]: any
[x: symbol]: any
[x: `head-${string}`]: string
[x: `${string}-tail`]: string
[x: `head-${string}-tail`]: string
[x: `${bigint}`]: string
[x: `embedded-${number}`]: string
normal = 123
optional?: string
}
type RemoveIndex<T> = {
[K in keyof T as {} extends Record<K, 1> ? never : K]: T[K]
}
type Result = RemoveIndex<X>
// ^? - { normal: number, optional?: string }
1 You can detect some infinite template literals by using a recursive type that processes one character at a time, but this doesn't work for long keys.
Solution 3:[3]
There is not a really generic way for it, but if you know which properties you need, then you can use Pick:
interface Foo {
[key: string]: any;
bar(): void;
}
type FooWithOnlyBar = Pick<Foo, 'bar'>;
const abc: FooWithOnlyBar = { bar: () => { } }
abc.notexisting = 5; // error
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | |
| Solution 2 | Gerrit0 |
| Solution 3 | jmattheis |
