Reputation: 7361
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
?
Upvotes: 384
Views: 291917
Reputation: 3928
For keyed access, if typescript can't predict the outcome, it throws the towel. Even if you don't care, or all values would be the same type anyway.
You can convert your type into something that allows random access. Looks like this:
export function randomAccess<T extends object>(object: T): Record<symbol | number | string, T[keyof T] | undefined> & T {
return object as any;
}
Used like:
interface ISomeObject {
firstKey: string;
secondKey: string;
thirdKey: string;
}
let someObject: ISomeObject = {
firstKey: 'firstValue',
secondKey: 'secondValue',
thirdKey: 'thirdValue'
};
let key: string = 'secondKey';
const secondValue: string | undefined = randomAccess(someObject)[key];
If you use a minifier like esbuild or terser, this extra function call will disappear and cause no overhead.
IMO it's important that randomAccess(obj)[key]
yields string | undefined
and not string
, because if your key
is indeed entirely arbitrary and not pre-sanitized, you should always assume that you got no match and the result may be undefined
.
Upvotes: 1
Reputation: 4272
You can only use types when indexing, meaning you can’t use a const to make a variable reference:
type Person = { age: number; name: string; alive: boolean };
const key = "age";
type Age = Person[key];
Type 'any' cannot be used as an index type.
use types to refer props
type key = "age";
type Age = Person[key];
type Age = number
Upvotes: -2
Reputation: 44413
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.
Upvotes: 1
Reputation: 8766
I had two interfaces. First was child of other. I did following:
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;
}
Upvotes: 0
Reputation: 1820
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];
Upvotes: 7
Reputation: 1605
use keyof typeof
const cat = {
name: 'tuntun'
}
const key: string = 'name'
cat[key as keyof typeof cat]
Upvotes: 45
Reputation: 146180
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.
Upvotes: 8
Reputation: 146180
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 :-)
Upvotes: 8
Reputation: 573
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} />
Upvotes: 3
Reputation: 527
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];
Upvotes: 2
Reputation: 6840
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.
Upvotes: 115
Reputation: 659
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];
}
Upvotes: 32
Reputation: 3191
Similar to @Piotr Lewandowski's answer, but within a forEach
:
const config: MyConfig = { ... };
Object.keys(config)
.forEach((key: keyof MyConfig) => {
if (config[key]) {
// ...
}
});
Upvotes: 7
Reputation: 1148
Declare the object like this.
export interface Thread {
id:number;
messageIds: number[];
participants: {
[key:number]: number
};
}
Upvotes: 8
Reputation: 13596
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.
Upvotes: 72
Reputation: 2312
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.
Upvotes: 192
Reputation: 8383
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.
Upvotes: 406