26

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])

Stuart
  • 307
  • 4
  • 9

1 Answers1

24

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]);
}
Meligy
  • 35,654
  • 11
  • 85
  • 109
  • Thanks @Meligy. Yeah you're right I could have made the index signature more specific in my first example, but that wasn't really what I was interested in. My question is more around how to add the index signature to the second example – Stuart Nov 24 '17 at 01:06
  • If you don't want too much ceremony (and maybe casting to `any` as a middle casting, then casting to something else to not loose typing completely), then there's not much to do. I assume the 2nd example was to solve the problem of the first, right? I added another way to do it, let me know your thoughts. – Meligy Nov 24 '17 at 01:12
  • If your target is to make the first example generic, I added a 3rd example for that too. – Meligy Nov 24 '17 at 01:15
  • 1
    Nice one! I figured that was probably the best way to do it. I find it a little disappointing that `Object.keys(x)` has type `string[]` instead of `(keyof T)[]` although the core contributors' objections to doing it that way do make sense too – Stuart Nov 24 '17 at 01:26
  • sweet jesus, thank you @Meligy . I've been trying to solve this for _days_! in case someone is trying to do this with a string enum: `enum eFoo { a = 'a', b = 'b' }; interface Foo { [key: string]: eFoo[keyof eFoo]; }` https://www.typescriptlang.org/play/index.html#src=enum%20eX%20%7Bdex%20%3D%20'dex'%2C%20str%20%3D%20'str'%7D%3B%0D%0A%0D%0Ainterface%20X%20%7B%5Bkey%3A%20string%5D%3A%20eX%5Bkeyof%20eX%5D%7D%0D%0A%0D%0Aconst%20xs%3A%20X%20%3D%20%7B%7D%3B%0D%0A%0D%0Afor%20(let%20k%20in%20eX)%20xs%5Bk%5D%20%3D%2010%3B%0D%0A%0D%0Aconsole.log(xs%2Cxs.dex)%3B – Jakob Jingleheimer Apr 27 '18 at 12:21