Typescript: Index signature is missing in type

CastingTypescript

Casting 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.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionManu ArteroView Question on Stackoverflow
Solution 1 - CastingTim PerryView Answer on Stackoverflow
Solution 2 - CastingC. LewisView Answer on Stackoverflow
Solution 3 - CastingNikolay PodolnyyView Answer on Stackoverflow
Solution 4 - CastingmeblumView Answer on Stackoverflow
Solution 5 - CastingTim BaasView Answer on Stackoverflow
Solution 6 - CastingJöckerView Answer on Stackoverflow
Solution 7 - CastingAlexandre AnnicView Answer on Stackoverflow