2

I have an object and want to access it using a string variable, however I get the error:

no index signature with a parameter of type 'string' was found on type '{ A: number; B: number; C: number; }'

const obj = {
    "A":1,
    "B":2,
    "C":3
 }

 const f=(str:string)=>{
     const result = obj[str];
 }

However, shouldn't this compile and result have type number|undefined? Is the only way to overcome this to cast str to keyof typeof obj as described here? I also thought of doing if (Object.keys(obj).includes(str))... but that does not narrow the type of str to just valid keys.

Erik Philips
  • 53,428
  • 11
  • 128
  • 150
BobtheMagicMoose
  • 2,246
  • 3
  • 19
  • 27

2 Answers2

2

Ran into a similar issue recently, this is the solution that helped me with my problem: Is key-value pair available in Typescript?

So with your code example, I would do something like this.

type OBJ = {
  [key: string]: number
}

const obj: OBJ = {
  "A": 1,
  "B": 2,
  "C": 3
}

const f = (str: string) => {
  const result: number = obj[str];
}

Not too sure if this is the absolute best way to go about it though.

Michael Hoobler
  • 622
  • 5
  • 14
2

Object types in TypeScript are "open" or "extendible", not "closed" or "exact". A value of an object type is required to have known properties, but in general it is not prohibited from having extra, unknown properties. Open and extendible object types are very useful: they allow interface and class extension to form type hierarchies; so interface Bar extends Foo { extraProp: string } means that every Bar is also a Foo, despite Foo's definition not knowing anything about extraProp. But it does have implications that are surprising to developers. For now there is no real support for exact types (although excess property checking does make it seem otherwise, sometimes); see microsoft/TypeScript#12936 for the relevant feature request.

Let's examine the type of obj as inferred by the compiler:

/* const obj: {
    A: number;
    B: number;
    C: number;
} */

That means the compiler knows that obj has numeric properties at keys A, B, and C. But because object types are open, the compiler does not know that it lacks all other properties. And although the object literal assigned to obj does indeed lack any other properties, the type system does not keep track of such information. As far as the compiler is concerned, obj may or may not have all kinds of other properties. It will only let you access A, B, and C without warning (with --strict or --noImplicitAny at any rate), but if you index into it with some unknown string key, the compiler says "that could be anything".

The closest TypeScript can get to specifying the types of unknown properties is to use an index signature, like {[k: string]: number | undefined}.

You can't say "A, B, and C are of type number and all other properties are undefined" because that would require a different sort of index signature to represent "all other properties" (see microsoft/TypeScript#17867 for the relevant feature request). Instead you could say "A, B, and C are number and all properties are number | undefined. Or, if you don't really care about keeping track of A, B, and C, you could do the above-mentioned "all properties are string | undefined via the following type annotation:

const obj: {[k: string]: number | undefined} = {
    "A": 1,
    "B": 2,
    "C": 3
};

const f = (str: string) => {
    const result = obj[str]; // number | undefined
}

Now your function is known to return number | undefined, as you wanted.

Playground link to code

jcalz
  • 264,269
  • 27
  • 359
  • 360