I always compile TypeScript with the flag --noImplicitAny
. This makes sense as I want my type checking to be as tight as possible.
My problem is that with the following code I get the error:
Index signature of object type implicitly has an 'any' type
interface ISomeObject {
firstKey: string;
secondKey: string;
thirdKey: string;
}
let someObject: ISomeObject = {
firstKey: 'firstValue',
secondKey: 'secondValue',
thirdKey: 'thirdValue'
};
let key: string = 'secondKey';
let secondValue: string = someObject[key];
Important to note is that the idea is that the key variable comes from somewhere else in the application and can be any of the keys in the object.
I've tried explicitly casting the type by:
let secondValue: string = <string>someObject[key];
Or is my scenario just not possible with --noImplicitAny
?
Adding an index signature will let TypeScript know what the type should be.
In your case that would be [key: string]: string;
interface ISomeObject {
firstKey: string;
secondKey: string;
thirdKey: string;
[key: string]: string;
}
However, this also enforces all of the property types to match the index signature. Since all of the properties are a string
it works.
While index signatures are a powerful way to describe the array and 'dictionary' pattern, they also enforce that all properties match their return type.
Edit:
If the types don't match, a union type can be used [key: string]: string|IOtherObject;
With union types, it's better if you let TypeScript infer the type instead of defining it.
// Type of `secondValue` is `string|IOtherObject`
let secondValue = someObject[key];
// Type of `foo` is `string`
let foo = secondValue + '';
Although that can get a little messy if you have a lot of different types in the index signatures. The alternative to that is to use any
in the signature. [key: string]: any;
Then you would need to cast the types like you did above.
Another way to avoid the error is to use the cast like this:
let secondValue: string = (<any>someObject)[key];
(Note the parenthesis)
The only problem is that this isn't type-safe anymore, as you are casting to any
. But you can always cast back to the correct type.
ps: I'm using typescript 1.7, not sure about previous versions.
let secondValue: string = (someObject as any)[key];
TypeScript 2.1 introduced elegant way to handle this issue.
const key: (keyof ISomeObject) = 'secondKey';
const secondValue: string = someObject[key];
We can access all object property names during compilation phase by keyof
keyword (see changelog).
You only need to replace string
variable type with keyof ISomeObject
. Now compiler knows key
variable is allowed to contain only property names from ISomeObject
.
Full example:
interface ISomeObject {
firstKey: string;
secondKey: string;
thirdKey: number;
}
const someObject: ISomeObject = {
firstKey: 'firstValue',
secondKey: 'secondValue',
thirdKey: 3
};
const key: (keyof ISomeObject) = 'secondKey';
const secondValue: string = someObject[key];
// You can mix types in interface, keyof will know which types you refer to.
const keyNumber: (keyof ISomeObject) = 'thirdKey';
const numberValue: number = someObject[keyNumber];
Live code on typescriptlang.org (set noImplicitAny
option)
Further reading with more keyof
usages.
key
as const key = (keyof ISomeObject)
= 'second' + 'Key'
The following tsconfig setting will allow you to ignore these errors - set it to true.
suppressImplicitAnyIndexErrors Suppress noImplicitAny errors for indexing objects lacking index signatures.
--noImplicitAny
. Match perfectly op's question.
XMLHttpRequest
.
keyof
operator can help keep everything strict, see Piotr's answer!
use keyof typeof
const cat = {
name: 'tuntun'
}
const key: string = 'name'
cat[key as keyof typeof cat]
keyof typeof
as keyof
when you want to say that the key you're referring to belongs to an interface. Use as keyof typeof
when you don't have an interface for some object and want the compiler to guess the type of object you're referring to.
key: any
. Instead you should make the type of key
actually BE keyof typeof cat: const key: keyof typeof cat = 'name'
That way you're not blindly casting strings to make the compiler quiet. Or stop calling key a string, make its type inferred: const key = 'name'
. Or use as const
to guarantee the type is not string, in case you want key to be variable: let key = 'name' as const
. SO many better options than just lying to the compiler so it stays quiet.
key
maybe come from other places, such as user input or a parsed json.
key
is and pretend it's a key in cat
. In the example, the input is NOT user input or JSON, it's a very common use case and we should be showing people how to do it right and professional instead of hacking around type safety.
The 'keyof' solution mentioned above works. But if the variable is used only once e.g looping through an object etc, you can also typecast it.
for (const key in someObject) {
sampleObject[key] = someObject[key as keyof ISomeObject];
}
Declare the object like this.
export interface Thread {
id:number;
messageIds: number[];
participants: {
[key:number]: number
};
}
Create an interface to define the 'indexer' interface
Then create your object with that index.
Note: this will still have same issues other answers have described with respect to enforcing the type of each item - but that's often exactly what you want.
You can make the generic type parameter whatever you need : ObjectIndexer< Dog | Cat>
// this should be global somewhere, or you may already be
// using a library that provides such a type
export interface ObjectIndexer<T> {
[id: string]: T;
}
interface ISomeObject extends ObjectIndexer<string>
{
firstKey: string;
secondKey: string;
thirdKey: string;
}
let someObject: ISomeObject = {
firstKey: 'firstValue',
secondKey: 'secondValue',
thirdKey: 'thirdValue'
};
let key: string = 'secondKey';
let secondValue: string = someObject[key];
You can even use this in a generic constraint when defining a generic type:
export class SmartFormGroup<T extends IndexableObject<any>> extends FormGroup
Then T
inside the class can be indexed :-)
Dictionary
that represents { [key: string]: T }
, but if there ever is please edit this question to remove my ObjectIndexer
.
No indexer? Then make your own!
I've globally defined this as an easy way to define an object signature. T
can be any
if needed:
type Indexer<T> = { [ key: string ]: T };
I just add indexer
as a class member.
indexer = this as unknown as Indexer<Fruit>;
So I end up with this:
constructor(private breakpointResponsiveService: FeatureBoxBreakpointResponsiveService) {
}
apple: Fruit<string>;
pear: Fruit<string>;
// just a reference to 'this' at runtime
indexer = this as unknown as Indexer<Fruit>;
something() {
this.indexer['apple'] = ... // typed as Fruit
Benefit of doing this is that you get the proper type back - many solutions that use <any>
will lose the typing for you. Remember this doesn't perform any runtime verification. You'll still need to check if something exists if you don't know for sure it exists.
If you want to be overly cautious, and you're using strict
you can do this to reveal all the places you may need to do an explicit undefined check:
type OptionalIndexed<T> = { [ key: string ]: T | undefined };
I don't usually find this necessary since if I have as a string property from somewhere I usually know that it's valid.
I've found this method especially useful if I have a lot of code that needs to access the indexer, and the typing can be changed in just one place.
Note: I'm using strict
mode, and the unknown
is definitely necessary.
The compiled code will just be indexer = this
, so it's very similar to when typescript creates _this = this
for you.
Record<T>
type instead - I'm not able right now to research the fine details of this but for some limited cases it may work better.
Similar to @Piotr Lewandowski's answer, but within a forEach
:
const config: MyConfig = { ... };
Object.keys(config)
.forEach((key: keyof MyConfig) => {
if (config[key]) {
// ...
}
});
Argument of type '(field: "id" | "url" | "name") => void' is not assignable to parameter of type '(value: string, index: number, array: string[]) => void'
. My code looks like so Object.keys(components).forEach((comp: Component) => {...}
, where Component
is a type (like MyConfig
).
Declare type which its key is string and value can be any then declare the object with this type and the lint won't show up
type MyType = {[key: string]: any};
So your code will be
type ISomeType = {[key: string]: any};
let someObject: ISomeType = {
firstKey: 'firstValue',
secondKey: 'secondValue',
thirdKey: 'thirdValue'
};
let key: string = 'secondKey';
let secondValue: string = someObject[key];
The simplest solution that I could find using Typescript 3.1 in 3 steps is:
1) Make interface
interface IOriginal {
original: { [key: string]: any }
}
2) Make a typed copy
let copy: IOriginal = (original as any)[key];
3) Use anywhere (JSX included)
<input customProp={copy} />
At today better solution is to declare types. Like
enum SomeObjectKeys {
firstKey = 'firstKey',
secondKey = 'secondKey',
thirdKey = 'thirdKey',
}
let someObject: Record<SomeObjectKeys, string> = {
firstKey: 'firstValue',
secondKey: 'secondValue',
thirdKey: 'thirdValue',
};
let key: SomeObjectKeys = 'secondKey';
let secondValue: string = someObject[key];
I had two interfaces. First was child of other. I did following:
Added index signature in parent interface. Used appropriate type using as keyword.
Complete code is as below:
Child Interface:
interface UVAmount {
amount: number;
price: number;
quantity: number;
};
Parent Interface:
interface UVItem {
// This is index signature which compiler is complaining about.
// Here we are mentioning key will string and value will any of the types mentioned.
[key: string]: UVAmount | string | number | object;
name: string;
initial: UVAmount;
rating: number;
others: object;
};
React Component:
let valueType = 'initial';
function getTotal(item: UVItem) {
// as keyword is the dealbreaker.
// If you don't use it, it will take string type by default and show errors.
let itemValue = item[valueType] as UVAmount;
return itemValue.price * itemValue.quantity;
}
There is no need to use an ObjectIndexer<T>
, or change the interface of the original object (like suggested in most of the other answers). You can simply narrow the options for key to the ones that are of type string using the following:
type KeysMatching<T, V> = { [K in keyof T]: T[K] extends V ? K : never }[keyof T];
This great solution comes from an answer to a related question here.
Like that you narrow to keys inside T that hold V values. So in your case to to limit to string you would do:
type KeysMatching<ISomeObject, string>;
In your example:
interface ISomeObject {
firstKey: string;
secondKey: string;
thirdKey: string;
}
let someObject: ISomeObject = {
firstKey: 'firstValue',
secondKey: 'secondValue',
thirdKey: 'thirdValue'
};
let key: KeysMatching<SomeObject, string> = 'secondKey';
// secondValue narrowed to string
let secondValue = someObject[key];
The advantage is that your ISomeObject
could now even hold mixed types, and you can anyway narrow the key to string values only, keys of other value types will be considered invalid. To illustrate:
interface ISomeObject {
firstKey: string;
secondKey: string;
thirdKey: string;
fourthKey: boolean;
}
let someObject: ISomeObject = {
firstKey: 'firstValue',
secondKey: 'secondValue',
thirdKey: 'thirdValue'
fourthKey: true
};
// Type '"fourthKey"' is not assignable to type 'KeysMatching<ISomeObject, string>'.(2322)
let otherKey: KeysMatching<SomeOtherObject, string> = 'fourthKey';
let fourthValue = someOtherObject[otherKey];
You find this example in this playground.
cause
You can only use types when indexing, meaning you can’t use a const to make a variable reference:
example
type Person = { age: number; name: string; alive: boolean };
const key = "age";
type Age = Person[key];
result
Type 'any' cannot be used as an index type.
Solution
use types to refer props
example
type key = "age";
type Age = Person[key];
result
type Age = number
Success story sharing