5

The following TypeScript sample code shows an error Element implicitly has an 'any' type because type '{one: number; two: number;}' has no index signature in the line const one = obj[prop]; in strict mode.

The compiler allows the line const two = obj[propName];, so I cannot understand why the error is shown or how to generally speaking access a property of an object using the bracket notation.

const obj = { one: 1, two: 2 };

const props = { one: 'one', two: 'two' };

// it is not possible add or change any properties in the props object 
props.zero = 'zero';
props.one = 1;

// prop has the type string
const prop = props.one;

// using the bracket notation fails with the following error message:
// Element implicitly has an 'any' type because type '{one: number; two: number;}' has no index signature.
// const prop: string
const one = obj[prop];

// this works because propName is of type 'two'
const propName = 'two';
const two = obj[propName];
doberkofler
  • 9,511
  • 18
  • 74
  • 126

3 Answers3

12

Element implicitly has an 'any' type because type '{one: number; two: number;}' has no index signature

Your object has no index signature, it has two named properties.

The compiler allows the line const two = obj['two'];

It allows the names of your properties, that's why obj['two'] and obj['one'] will work.

and the const prop is a string, so I cannot understand why the error is shown

Because a string can have much more values that just 'one' or 'two' and so the compiler cannot ensure, your object[myString] call will work. It's only valid for two defined string values.

how to generally speaking access a property of an object using the bracket notation.

This would work:

 const prop: 'one' | 'two' = 'one';
 const test = obj[prop]

You say: prop has value 'one' or 'two' and so the compiler knows your obj[prop] will always be valid.

or

 class Example {
   one: string;
   two: string;
 }
 const prop: keyof Example = 'one';
 const test = obj[prop];

Here keyof(ClassName) tells the compiler, that your prop var will have an existing property name of Example.

The above examples assume, that you have an object where only the properties named 'one' and 'two' are valid. If you want to use your object in a "dictionary style", tell typescript about that and add an index signature:

 const obj: {[key:string]: string;} = { one: 'one' };
 const text = obj[myString];

Now obj allows every string values as key.

Christoph Lütjen
  • 5,403
  • 2
  • 24
  • 33
  • 1
    Thank you. I starting to understand. The problem is not the fact that I use the using the bracket notation by itself but rather the expression that I'm using. What still confuses me a little, is why the compiler does not consider the fact that my props object actually only consists of two properties that contain the property names used in obj? – doberkofler May 02 '19 at 20:15
  • `props.one`is of type string (that's simply the default when you assign a value without specifying a type as you do it with `{one: 'one' }`). You didn't tell the compiler more about it. – Christoph Lütjen May 02 '19 at 20:19
  • shouldn't it be `const prop: keyof Example = 'one';`? – Ionel Lupu May 02 '19 at 20:22
  • Correct, but in `const propName = 'two'` propName is also just of string and the compiler seems to accept it. This is exactly what got me lost. – doberkofler May 02 '19 at 20:24
  • @doberkofler: According to my VS hints, `const t = 'one'` results in `t: 'one'`. So, no - it's not a string. (While `const o = {one: 'one'}` results in `o.one: string`. – Christoph Lütjen May 02 '19 at 20:33
  • @ChristophLütjen You are absolutely right. I overlooked that type type of `propName` in `const propName = 'two'` is actually `'two'`. What I still do not understand is why TypeScript cannot infer the same information when using the properties of the `props` object? – doberkofler May 02 '19 at 21:44
  • thanks mann @ChristophLütjen ! you saved my day – singhkumarhemant Jan 17 '22 at 09:24
  • The reason the example code doesn't work is that the type of `props` is inferred (not explicit) and for convenience it is inferred to `{one: string, two: string}` because usually that's what we mean. Not specific strings, any string. The compiler can be told you mean these exact strings by adding "as const": `const props = { one: 'one', two: 'two' } as const;`. Now the type of props.one is `"one"` and you can use it to index `obj` directly. – Vectorjohn May 18 '22 at 22:39
0

As has been mentioned, the inferred type of props is {one: string, two: string}. This means props.one could be any string (even though we see it is "one"), so when you write obj[props.one] what you're really accessing is obj[string] which may not be allowed.

If you made your props an explicit type, you could get around it: type Props = {one: "one", two: "two"}, and then use it: const props: Props = {one: "one", two: "two"}

This way the type of props is not inferred, and the explicit type of the values is "one" or "two".

But that's annoying to write when what you really want to say is that props is these exact values. You can tell the compiler not to be clever about the type it infers and use the exact values you specified with as const:

const obj = { one: 1, two: 2 };
const props = { one: 'one', two: 'two' } as const;

const prop = props.one;

const one = obj[prop];

Now the compiler knows that prop has the exact value "one" instead of any string. So it is allowed.

Vectorjohn
  • 411
  • 2
  • 8
-1

Particularly I'm fan of strong type, but const obj: any should be ok.


interface test { 
    one: string;
    two: string;
}

const props: test = { one: 'one', two: 'two' }; // {one: string, two: string}
const obj: any = {one: 1, two: 2}; // {one: number, two: number}


const two = obj['two']; // number

const prop = props.one; // string

const one = obj[prop];

Enum Solution

enum numbers {
    one = 1,
    two = 2
}

const oneStr = numbers[numbers.one];
const one = numbers[oneStr];

  • I understand that using a type any on obj would solve this error but this would also completely remove any type checking on obj itself and therefore be quite the opposite of a string typing. Maybe I just did not understand and you could elaborate on your solution. – doberkofler May 02 '19 at 20:19
  • 1
    Using `any` completely defeats the purpose of using TypeScript.. Yes, there are times when you kind of need `any`, but generally speaking, if you are just going to use `any` everywhere, just write es6 and ditch typescript because you're using it wrong. – mwilson May 02 '19 at 20:26
  • My idea is not refactoring the whole post, is only solve the compilation issue. I prefer using strong type. Indeed it is possible create a solution only using `enums`, but the original post can be an simplified scenery. I'm trying to follow the rule, if some ask A I'm answer A, in some case suggest B. – Agustin Eloy Barrios May 02 '19 at 20:58
  • @AgustinEloyBarrios ok, but how would you then solve this with an `enum`? – doberkofler May 03 '19 at 14:08