Typescript: An index signature parameter type must be 'string' or 'number' - why not string | number?

Created on 10 Jan 2017  路  4Comments  路  Source: microsoft/TypeScript

This:

interface A {
    [key: string | number]: boolean;
}

specifies that the key must be either a string or a number, but produces an error saying that the key must be either string or number! But I guess this helps with clarity, ensuring that a value inserted with a string key "123" is not read with a number key 123 and vice versa.

But in the generic case:

interface A<K extends string|number> {
    [key: K]: boolean;
}

I could instantiate this with K = string|number, but more likely the intent is to pass one or the other, in which case this seems totally reasonable, but we currently can't do it.

Question

Most helpful comment

I am not sure why this was closed?

interface MyMap<T extends string> {
    [key: T]: any
}

I should be able to do something like this to restrict. I know this won't make any sense what I wanted to do is something like this

interface MyMap<T> {
    [key: K]: MyType<K, T>
}

which basically every key would have the same key type.
This is allowed in functional interface

interface MyMapFn<T> {
    <K>(k:K): MyType<K,T>
}

Why can't i do something like this ?

All 4 comments

But I guess this helps with clarity, ensuring that a value inserted with a string key "123" is not read with a number key 123 and vice versa.

Actually you can get a value inserted with a string key "123" by using the number key 123, but the reverse will result in an any.
Consider

interface Keyed {
  [key: string]: {};
}
interface Numbered { // This is just named "Numbered" for fun ;)
  [key: number]: {};
}

const x: Keyed = {};
const y: Numbered = {};

const a = x[1]; // {}
const b = y['1']; // any

Yes. number is convertible to string, so really (in the first example) string | number would effectively be equivalent to string, so in truth the existing behaviour doesn't help that much in the simple case, though with --noImplicitAny the reverse is properly caught.

This looseness extends to access via union key type:

declare const m: { [key: string ]: boolean};
declare const k: string | number;

m[k] = true;

However, the generic case (which is what I'm actually interested in) is a bit tighter:

function foo<K extends string | number>(k: K) {

    var m: { [key: string ]: boolean}; // try using string as workaround

    m[k] = true; // type K cannot be used to index...
}

So the indexer key type is string, and K is one of (string|number), string, number, all of which are fine outside generics. But instead it errors.

So the complete workaround is to use string as the key type and stick in a type assertion m[k as string] everywhere you use the indexer. Or equivalently, use number and use m[k as number] everywhere. It doesn't matter which one you pick, as in the generic case the convertibility of number to string does not appear to be relevant.

My guess is that if an indexer could be declare with a generic key type, as long as that key type was one of (string|number), string, number, then no workaround would be needed.

I am not sure why this was closed?

interface MyMap<T extends string> {
    [key: T]: any
}

I should be able to do something like this to restrict. I know this won't make any sense what I wanted to do is something like this

interface MyMap<T> {
    [key: K]: MyType<K, T>
}

which basically every key would have the same key type.
This is allowed in functional interface

interface MyMapFn<T> {
    <K>(k:K): MyType<K,T>
}

Why can't i do something like this ?

I don't understand why this was closed too, but I did figure out how to work around the problem.

You basically can not use the OR | operator for types on a key, but you can define overloaded accessors of different types.

interface A<T extends string | number> {
    [key: string]: any;
    [key: number]: any;
}

If you are using the generics type to define the accessor for a function parameter. You have to define both types as string or number like this:

interface A<T extends string | number> {
    get(data: {[key:string]:any} | {[key :number]:any})
}

I see this as just a work around. If anyone has a better approach please share it.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

kyasbal-1994 picture kyasbal-1994  路  3Comments

Antony-Jones picture Antony-Jones  路  3Comments

weswigham picture weswigham  路  3Comments

blendsdk picture blendsdk  路  3Comments

CyrusNajmabadi picture CyrusNajmabadi  路  3Comments