Monday, May 20, 2024
 Popular · Latest · Hot · Upcoming
90
rated 0 times [  97] [ 7]  / answers: 1 / hits: 7473  / 4 Years ago, tue, january 28, 2020, 12:00:00

Why can TypeScript index a typed object by string when that string is a constant or a simple string variable, but it's unable to index a typed object by a string if that string is pulled out of an array



That is, consider the following code



class Foo {
public bar: string = 'hello';

public test() {
// this works
console.log(this['bar'])

// this also works
const index = 'bar';
console.log(this[index])

// in both cases above I have successfully used
// a string as an index for my type Foo

// However, this doesn't work
const props:string[] = ['bar']
for(const [key,value] of props.entries()) {
console.log(value); // prints 'bar' to terminal/console
console.log(this[value])
}

// Nor does this
for(let i=0;i<props.length;i++) {
console.log(this[props[i]])
}

// when looping over an array of string and trying to use the
// string to index the object, I get the following error
// why.ts:20:25 - error TS7053: Element implicitly has an 'any'
// type because expression of type 'string' can't be used to
// index type 'Foo'.
}
}

const foo = new Foo;
foo.test()


class Foo {
public bar: string = 'hello';

public test() {
// this works
console.log(this['bar'])

// this also works
const index = 'bar';
console.log(this[index])

// in both cases above I have successfully used
// a string as an index for my type Foo

// However, this doesn't work
const props:string[] = ['bar']
for(const [key,value] of props.entries()) {
console.log(value); // prints 'bar' to terminal/console
console.log(this[value])
}

// Nor does this
for(let i=0;i<props.length;i++) {
console.log(this[props[i]])
}

// when looping over an array of string and trying to use the
// string to index the object, I get the following error
// why.ts:20:25 - error TS7053: Element implicitly has an 'any'
// type because expression of type 'string' can't be used to
// index type 'Foo'.
}
}

const foo = new Foo;
foo.test()


Both of these work.



console.log(this['bar'])
//...
const index = 'bar';
console.log(this[index])


TypeScript's able to index my object by a string.



However, the later examples where I'm looping over a string array



const props:string[] = ['bar']
for(const [key,value] of props.entries()) {
console.log(value); // prints 'bar' to terminal/console
console.log(this[value])
}

for(let i=0;i<props.length;i++) {
console.log(this[props[i]])
}


won't run/compile. I get the following error.



why.ts:42:17 - error TS7053: Element implicitly has an 'any' type because expression of type 'string' can't be used to index type 'Foo'.
No index signature with a parameter of type 'string' was found on type 'Foo'.

42 console.log(foo[value])


So this error message -- expression of type 'string' can't be used to index type 'Foo' seems to run counter to my first two examples.



So what's going on here? Help a poor dynamic language programmer understand what TypeScript is trying to tell me. Bonus points for a sample that actually allows me to iterate over an array of strings and use one as an object index.


More From » typescript

 Answers
11

The answer is simple, if typescript can prove the access is safe, the indexing is allowed.



When you write this['bar'] typescript sees the string literal and it can trivially check that this has a property bar



When you write const index = 'bar'; you may think the type of index is string but it is actually not, the type of index is the string literal type 'bar', so typescript will know the only possible value in index is 'bar'. Since index can only hold bar, typescript can check that the access this[index] is valid by checking this has a property bar



When you write const props:string[] typescript will not make any other inferences about props it is an array of string. This means when you access this[prop] typescript needs to be sure this is indexable by any string, which since it does not have an index signature, it is not and thus the access throws an error. If you use as const to make ts infer literal types for the array instead of string and remove the explicit annotation, you will be able to perform the index access:



const props = ['bar'] as const
for(const [key,value] of props.entries()) {
console.log(value);
console.log(this[value])//ok
}

for(let i=0;i<props.length;i++) {
console.log(this[props[i]])
}



Playground Link



You can also use a type assertion if you are sure prop is a key of this:



const props = ['bar']
for(const [key,value] of props.entries()) {
console.log(this[value as keyof this])
}


Or if you want to get really fancy you can use a custom type guard or custom type assertion, but that seems like overkill here.


[#4916] Saturday, January 25, 2020, 4 Years  [reply] [flag answer]
Only authorized users can answer the question. Please sign in first, or register a free account.
hailey

Total Points: 355
Total Questions: 91
Total Answers: 91

Location: India
Member since Wed, Aug 4, 2021
3 Years ago
hailey questions
Sun, Jul 11, 21, 00:00, 3 Years ago
Mon, Nov 2, 20, 00:00, 4 Years ago
Fri, Jul 24, 20, 00:00, 4 Years ago
Fri, Aug 30, 19, 00:00, 5 Years ago
Sun, Apr 28, 19, 00:00, 5 Years ago
;