Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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;

like image 696
Laurence Dougal Myers Avatar asked Jul 22 '18 12:07

Laurence Dougal Myers


2 Answers

Edit: Since Typescript 4.1 there is a way of doing this directly with Key Remapping, avoiding the Pick combinator. Please see the answer by Oleg where it's introduced.

type RemoveIndex<T> = {   [ K in keyof T as string extends K ? never : number extends K ? never : K ] : T[K] }; 

It is based on the fact that 'a' extends string but string doesn't extends 'a'.


There is also a way to express that with TypeScript 2.8's Conditional Types.

interface Foo {   [key: string]: any;   [key: number]: any;   bar(): void; }  type KnownKeys<T> = {   [K in keyof T]: string extends K ? never : number extends K ? never : K } extends { [_ in keyof T]: infer U } ? U : never;   type FooWithOnlyBar = Pick<Foo, KnownKeys<Foo>>; 

You can make a generic out of that:

// Generic !!! type RemoveIndex<T extends Record<any,any>> = Pick<T, KnownKeys<T>>;  type FooWithOnlyBar = RemoveIndex<Foo>; 

For an explanation of why exactly KnownKeys<T> works, see the following answer:

https://stackoverflow.com/a/51955852/2115619

like image 55
Mihail Malostanidis Avatar answered Sep 24 '22 18:09

Mihail Malostanidis


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; }

Playground

like image 45
Oleg Valter is with Ukraine Avatar answered Sep 24 '22 18:09

Oleg Valter is with Ukraine