Typescript: Index signature is missing in type
CastingTypescriptCasting Problem Overview
I want MyInterface.dic
to be like a dictionary name: value
, I define it as follows:
interface MyInterface {
dic: { [name: string]: number }
}
Now I create a function which waits for my type:
function foo(a: MyInterface) {
...
}
And the input:
let o = {
dic: {
'a': 3,
'b': 5
}
}
I'm expecting foo(o)
to be correct, but the compiler is falling:
foo(o) // Typescript error: Index signature is missing in type { 'a': number, 'b': number }
I know there is a possible casting: let o: MyInterface = { ... }
which do the trick but the question is, why typescript is not recognizing my type?
Extra: works fine if o
is declared inline:
foo({
dic: {
'a': 3,
'b': 5
}
})
Casting Solutions
Solution 1 - Casting
The problem is that when the type is inferred, then the type of o
is:
{ dic: { a: number, b: number } }
That's not the same as { dic: { [name: string]: number } }
. Critically, with the top signature you're not allowed to do something like o.dic['x'] = 1
. With the 2nd signature you are.
They are equivalent types at runtime (indeed, they're the exact same value), but a big part of TypeScript's safety comes from the fact that these aren't the same, and that it'll only let you treat an object as a dictionary if it knows it's explicitly intended as one. This is what stops you accidentally reading and writing totally non-existent properties on objects.
The solution is to ensure TypeScript knows that it's intended as a dictionary. That means:
-
Explicitly providing a type somewhere that tells it it's a dictionary:
let o: MyInterface
-
Asserting it to be a dictionary inline:
let o = { dic: <{ [name: string]: number }> { 'a': 1, 'b': 2 } }
-
Ensuring it's the initial type that TypeScript infers for you:
foo({ dic: { 'a': 1, 'b': 2 } })
If there's a case where TypeScript thinks it's a normal object with just two properties, and then you try to use it later as a dictionary, it'll be unhappy.
Solution 2 - Casting
TS wants us to define the type of the index. For example, to tell the compiler that you can index the object with any string, e.g. myObj['anyString']
, change:
interface myInterface {
myVal: string;
}
to:
interface myInterface {
[key: string]: string;
myVal: string;
}
And you can now store any string value on any string index:
x['myVal'] = 'hello world'
x['any other string'] = 'any other string'
Solution 3 - Casting
For me error was solved by using type instead of interface
This error can occur when function foo
has type instead of interface for typing parameter like
type MyType {
dic: { [name: string]: number }
}
function foo(a: MyType) {}
But passed value typed with interface like
interface MyInterface {
dic: { [name: string]: number }
}
const o: MyInterface = {
dic: {
'a': 3,
'b': 5
}
}
foo(o) // type error here
I just used
const o: MyType = {
dic: {
'a': 3,
'b': 5
}
}
foo(o) // it works
Solution 4 - Casting
You can solve this problem by doing foo({...o})
playground
Solution 5 - Casting
Here's my two cents:
type Copy<T> = { [K in keyof T]: T[K] }
genericFunc<SomeType>() // no index signature
genericFunc<Copy<SomeType>>() // no error
Solution 6 - Casting
In my case was just necessary to use type
instead of interface
Solution 7 - Casting
This error is legitimate. You should write this instead to mark types as immutable:
interface MyInterface {
dic: { [name: string]: number }
}
function foo(a: MyInterface) {
...
}
const o = Object.freeze({
dic: {
'a': 3,
'b': 5
}
})
Why ?
The TS compiler cannot assume that o
won't change between the time it is initialized and the time foo(o)
is called.
Maybe somewhere in your code something like the snippet below is written:
delete o.dic.a;
That's why the inline version works. In this case there is no update possible.