Typescript: access an object property with square bracket notation and variable
What is the correct way in TypeScript to access a Javascript object with [ ]
notation and using a variable (that we are initially unsure of its value) that evaluates to a string with the brackets to access an objects property, i.e. myObject[myVariable]
The following code errors as expected as we have an unknown value in x
so we can not be sure that it is a key of myDictionary (however, this isn't exactly the error, see code comment):
const myDictionary = {
key1: 'String 1',
key2: 'String 2',
key3: 'String 3',
};
const possibleValuesOfX = [
'key1',
'notAKey1',
'key2',
'notAKey2',
'key3',
'notAKey3',
];
// simulating a random value that could be a possible key of myDictionary
const x =
possibleValuesOfX[Math.floor(Math.random() * possibleValuesOfX.length)];
// The following errors: No index signature with a parameter of type 'string' was found on type '{ key1: string; key2: string; key3: string; }'.ts(7053)
const decode = myDictionary[x];
So I try the following, thinking I'm checking to ensure x
is a key of myDictionary
before attempting to access myDictionary
with x
const myDictionary = {
key1: 'String 1',
key2: 'String 2',
key3: 'String 3',
};
const possibleValuesOfX = [
'key1',
'notAKey1',
'key2',
'notAKey2',
'key3',
'notAKey3',
];
const x =
possibleValuesOfX[Math.floor(Math.random() * possibleValuesOfX.length)];
let decode: string;
if (x in myDictionary) {
// Still produces the same error as above
decode = myDictionary[x];
}
This doesn't stop the error.
Now I can stop the error with const myDictionary: { [key: string]: string }
but this also stops autocomplete from working with myDictionary
and provides little else from what I can see.
To keep auto complete and stop the error I can do the following:
type myDictionaryKeys = 'key1' | 'key2' | 'key3';
const myDictionary: Record<myDictionaryKeys, string> = {
key1: 'String 1',
key2: 'String 2',
key3: 'String 3',
};
const possibleValuesOfX = [
'key1',
'notAKey1',
'key2',
'notAKey2',
'key3',
'notAKey3',
];
const x =
possibleValuesOfX[Math.floor(Math.random() * possibleValuesOfX.length)];
let decode: string;
if (x in myDictionary) {
decode = myDictionary[x as myDictionaryKeys];
}
But this still feels 'hacky' and overly verbose as I'm casting x as myDictionaryKeys
to remove the error and having to type the keys out twice once in myDictionary
object and once in myDictionaryKeys
type.
Is there a 'proper' way to do this, or a cleaner way?
Edit: example updated in response to the question in your comment.
You can use a type predicate to inform the compiler that the value is a key in the dictionary, like the isDictionaryKey
function in this example:
TS Playground
function isDictionaryKey <T>(
dictionary: T,
str: string,
): str is keyof Omit<T, number | symbol> {
return str in myDictionary;
}
const myDictionary = {
key1: 'String 1',
key2: 'String 2',
key3: 'String 3',
};
const possibleValuesOfX = [
'key1',
'notAKey1',
'key2',
'notAKey2',
'key3',
'notAKey3',
];
const x = possibleValuesOfX[Math.floor(Math.random() * possibleValuesOfX.length)];
let decode: string;
if (x && isDictionaryKey(myDictionary, x)) {
x; // "key1" | "key2" | "key3"
decode = myDictionary[x];
}