Remove Index Signature
Challenge
Implement RemoveIndexSignature<T>
, exclude the index signature from object
types. For example:
type Foo = {
[key: string]: any;
foo(): void;
};
type A = RemoveIndexSignature<Foo>; // expected { foo(): void }
Solution
We are having a deal with the objects here. I bet we will use mapped types later. But for now, let us dive into the problem statement and figure out the requirement.
We have been asked to remove index signatures from the object types. How do they
look like? By calling keyof
operator we can take a peek into it.
E.g. having a type “Bar” and calling keyof
on it results in:
type Bar = { [key: number]: any; bar(): void }; // number | “bar”
Interesting, so it turns out that they represent any key on the object as a
string type literal. While the index signature has just a type like number
or
string
.
It leads me to the idea that we need to filter out any keys that are not type literals. But, how do we check if the type is a type literal or not? We can use the nature of sets and check if some set is a subset or not. For instance, the type literal “foo” extends from the string, but not otherwise, “foo” is too narrow to be a string.
"foo" extends string // true
string extends "foo" // false
Let’s use it and create a type that will filter out strings and numbers, leaving
only type literals. First, we will check the case with the string
:
type TypeLiteralOnly<T> = string extends T ? never : never;
In case T
is a string
, the condition evaluates to true
. What do we do if
we got a string
? We skip it by returning the never
type. Otherwise, let us
check the same for the number
type:
type TypeLiteralOnly<T> = string extends T
? never
: number extends T
? never
: never;
What if T
is neither a string
nor number
? It means we got a type literal
here, so we return it back to the caller:
type TypeLiteralOnly<T> = string extends T
? never
: number extends T
? never
: T;
Now, we have a wrapper that can filter out index signature and return type literals only. How do we apply it on each key? By using mapped types!
At first, let us create a copy of the type:
type RemoveIndexSignature<T> = { [P in keyof T]: T[P] };
While iterating on the keys, we can remap them by using our wrapper. We call
TypeLiteralOnly
on each key:
type RemoveIndexSignature<T> = { [P in keyof T as TypeLiteralOnly<P>]: T[P] };
That way, we have an iteration on the keys that filters out index signature, leaving type literals only.
type TypeLiteralOnly<T> = string extends T
? never
: number extends T
? never
: T;
type RemoveIndexSignature<T> = { [P in keyof T as TypeLiteralOnly<P>]: T[P] };
Comments