Search code examples
typescriptmapped-typesindex-signature

How do I add an index signature for a mapped type?


Suppose I have interface

interface X {
   a: string;
   b: number;
   c: boolean;
}

and a function

function values(x: X) {
   return Object.keys(x).map(s => x[s])
}

When I enable typescript's strict flag I get the error "Element implicitly has an 'any' type because type 'X' has no index signature". So to make it explicit, I can just add an index signature to the definition of X

[key: string]: any;

Easy peasy.


However if I X is now a mapped type instead:

type X<T> = {
  [P in keyof T]: string;
}

and I have the function

function values<T>(x: X<T>) {
  return Object.keys(x).map(s => x[s])
}

where am I supposed to add the index signature? Is there any way to make this explicit without resorting to doing something gross like Object.keys(x).map(s => (x as any)[s])


Solution

  • You can:

    interface X {
        a: string;
        b: number;
        c: boolean;
        [key: string]: X[keyof X];
    }
    

    The result of X[keyof X] will now be (string | number | boolean), which works even better than any because the return of your function will be (string | number | boolean)[].

    Example

    Another way that should work with both examples is:

    function values(x: X) {
        const keys = Object.keys(x) as (keyof X)[];
        return keys.map(s => x[s]);
    }
    

    Not pretty, but at least more typed than (x as any).

    Of course it can be made generic too:

    function values<T>(x: T) {
        const keys = Object.keys(x) as (keyof T)[];
        return keys.map(s => x[s]);
    }