3

I'm trying to make the key in the interface declaration generic so that if I pass the interface a string, the key will be a string.

K is erroring: An index signature parameter type must be either 'string' or 'number'.

interface SomeObj<K , V> {
  bar: {
    [P in K]: V
  }
};


const foo: SomeObj<string, number> = { 
  bar: {hello: 1234}
}
Hossam Mohamed
  • 126
  • 1
  • 7
  • You want a mapped type instead of an index signature... it looks like `{[P in K]: V}` instead of `{[k: K]: V}`. This is equivalent to `Record`. See the [answer](https://stackoverflow.com/a/59331699/2887218) to the linked question for more information. – jcalz Nov 27 '21 at 20:59
  • That won't work either because `P in K` will treat K as a union of string values. I'm looking to get the type of `k` not the value. Something like [`k: string]: V]` that will work but if I replace `string with `K`. It won't be happy. – Hossam Mohamed Nov 27 '21 at 21:20
  • Well I’ve reopened the question but I don’t understand how `Record` doesn’t work for you. You say “it won’t work”… have you tried it? – jcalz Nov 27 '21 at 21:23
  • Make sure to update the error to "`Type 'K' is not assignable to type 'string | number | symbol'`" instead of "`An index signature parameter type must be either 'string' or 'number'`". – jcalz Nov 28 '21 at 03:20

1 Answers1

1

I'm going to be answering my own question here.

Whenever we are structuring an "object key type" we are constrained by the types that typescript defined which are string, number, symbol and those are also the only types accepted as a javascript object key.

The compiler didn't like the generic type K only without the extend because it can literally be anything e.g null and that's not a valid key value.

Therefore, if we want to constrain the type of the key to an acceptable type we pass in our object declaration. we have to first tell the generic type in this case K which types it can be in this case string | number.

interface SomeObj<K extends string | number , V> {
  something: {
    [key in K]: V;
  }
};

const foo: SomeObj<string, number> = { 
  something: {hello: 1234}
}
const bar: SomeObj<number, number> = { 
  something: {123: 1234}
}
Hossam Mohamed
  • 126
  • 1
  • 7
  • even if `K extends string | number` (or you can write `K extends PropertyKey` to include `symbol`), `{[k: K]: V}` will fail to compile. This answer jumps to using mapped types of the form `{[P in K]: V}` without mentioning it. Either you should probably change the question to be using `[P in K]` instead of `[k: K]`, or you should probably change the answer to go into the difference between index signatures (whose keys cannot be generic) and mapped types (whose keys can). – jcalz Nov 28 '21 at 02:58
  • 1
    Also note that the convention is not `[key in K]`, since what you're calling `key` is a type parameter, and as such should be capitalized and short. So `[P in K]` is better. In an index signature like `[key: string]`, the `key` is a dummy key *name* and thus should take the form of a JavaScript identifier for keys, like something lowercase. By writing `key in K` you're making it look like `key` is a property key identifier and not a type parameter name. – jcalz Nov 28 '21 at 03:00
  • You're right, thank you. Will update the question now. – Hossam Mohamed Nov 28 '21 at 03:15