TypeScript - ts(7053) : Element implicitly has an 'any' type because expression of type 'string' can't be used to index
Asked Answered
I

5

17

In TypeScript, I declare an interface like this:

export default interface MyDTO {
    readonly num: string;
    readonly entitle: string;
    readonly trb: string;
    readonly ucr: string;
    readonly dcr: string;
    readonly udm?: string;
    readonly ddm?: string;
}

With a function, I would like to access the value of a property, whose name is contained in a variable.

private doSomething(dto: MyDTO, property: string): any {
    let label: any;

    if (['dcr', 'ddm'].includes(property)) {
        label = doSomethingElse(dto[property]);
    } else {
        label = dto[property];
    }
    
    return label;
}

Unfortunately, TypeScript gives me the following error message :

Element implicitly has an 'any' type because expression of type 'string' can't be used to index type 'MyDTO'. No index signature with a parameter of type 'string' was found on type 'MyDTO'.ts(7053)

Anyone have an idea, please ?

Thank you

Implicate answered 6/7, 2020 at 15:37 Comment(1)
C
24

The reason for this is because MyDTO has explicitly named properties, but you're using a generic string as an index, so TypeScript is saying that it can't guarantee that whatever string is passed into your doSomething function will actually match a property name on your interface.

An excellent workaround for this that was introduced in TypeScript 2.1 is keyof. This allows you to explicitly type something as a key of a certain class/interface.

This will A. get rid of the TS error you're seeing, and B. also check to make sure that any callers of your function actually pass a valid key.

export default interface MyDTO {
    readonly num: string;
    readonly entitle: string;
    readonly trb: string;
    readonly ucr: string;
    readonly dcr: string;
    readonly udm?: string;
    readonly ddm?: string;
}

function doSomething(dto: MyDTO, property: keyof MyDTO): any {
    let label: any;

    if (['dcr', 'ddm'].includes(property)) {
        label = doSomethingElse(dto[property]);
    } else {
        label = dto[property];
    }
    
    return label;
}

doSomething(obj, "foo") // is a TS error
doSomething(obj, "num") // is valid
Coltun answered 6/7, 2020 at 15:47 Comment(5)
Indeed this corrects me the case. But I have a somewhat special case, the value I have in property is not a key to my DTO. This can be a simple string that allows me to make an operation from the property of my DTO but I do not directly recover the property of my object. For example : if (property === 'test') { label = property.num + '-' + property.entitle; }. How to combine the keyof and the type string and that in the code of my function that does not concern me about the type?Implicate
I can do before a test on the type entered : if(typeof property === 'string').Implicate
How can I test if the value of property is a key to my object ? If I test the type of my object (type string), I always pass even if it is a key to my object.Implicate
@Implicate I'm not sure I understand the question. Can you update your post with a full example of what is not working for you with this solution? It seems like you're wanting to know if the value of a property on your object is also a key to your object?Coltun
Saved my bacon. Thanks for this!Samellasameness
I
0

@mhodges, with your suggestions, here is my modified function which seems to work well. However in the following case, I had to add the "as string" otherwise I have the following error:

Type 'string | keyof V 'cannot be used to index type' V'.ts (2536)

public getDefaultComparator(property: keyof V | string, v1: V, v2: V): number {
    let compareReturn = 0;
    if (v1.hasOwnProperty(property)) {
      const compareValue1 = v1[property as string];
      const compareValue2 = v2[property as string];
      if (compareValue1 > compareValue2) {
        compareReturn = 1;
      } else if (compareValue1 < compareValue2) {
        compareReturn = -1;
      }
    }

    return compareReturn;
  }
Implicate answered 8/7, 2020 at 15:14 Comment(0)
A
0

Needed a simpler solution:

const key = property as keyof typeof MyDTO;
label = dto[key] ?? 'default value'

Followed Solution 1 from here.

Archegonium answered 7/1 at 20:31 Comment(0)
W
0

If your type is guaranteed to be MyDto you can fix your issue with the 'keyof' type operator:

private doSomething(dto: MyDTO, property: keyof MyDTO): string {
  let label: any;

  if (['dcr', 'ddm'].includes(property)) {
    label = doSomethingElse(dto[property]);
  } else {
    label = dto[property];
  }
  
  return label;
}

This way, only string keys of MyDto will be allowed to be passed to your function.

If you can't guarantee the property value type (at compilation time), since you are receiving it from a external source for example you can do the following to correct index the type:

function isKeyOfType<T extends Record<string, any>>(obj: T, property: unknown): property is (keyof T) {
  // REMINDER! 'in' operator only works with Objects, because it's similar to check using '.hasOwnProperty'
  return typeof property === 'string' && property in obj;
}

private doSomething(dto: MyDTO, property: string): string {
  let label: any;

  if (!isKeyOfType(dto, property)) {
    return ''; // Validate Exceptions
  }

  if (['dcr', 'ddm'].includes(property)) {
    label = doSomethingElse(dto[property]);
  } else {
    label = dto[property];
  }
  
  return label;
}

This is possible because since typescript v3.7 (see release notes) it's possible to create user type guards for narrowing type inference in TypeScript code.

Also you can use the 'in' keyword that also narrow the type to help you validate manually the object in the guard in a runtime type safe way. However it's important to remember that in a production environment sometimes could be a better solution rely in a solution like zod, ajv.js or even class-validator/transformer, for schema/object validation.

The only other thing you need to assert in this case is to remember that if the property is not defined in the object, this will fail because the property is not in the runtime object. But this is easy to solve, one way you can solve that is using a object with all the default values of your interface (convert it to a class), or ignore this validation section and just return true for the assertion and it'll work.

Other Sources:

Woodie answered 4/4 at 6:57 Comment(0)
P
-17

In the tsconfig.json file. Set "strict": true --> false.

This worked for me.

Pewee answered 13/4, 2022 at 13:25 Comment(2)
True, and I got the same error after creating a tsconfig.json file.. The default behavior is strict: true. But I guess setting it to false will just not shine light on the problem. Like out of view, out of problem. If you just want a quick and dirty fix for a small hobby or test, you could do this. But infact you have some problem on your logic.Maximilien
Setting strict to false is not something that should be encouraged.Inextensible

© 2022 - 2024 — McMap. All rights reserved.