2

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?

1 Answer 1

2

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];
}
Sign up to request clarification or add additional context in comments.

2 Comments

Thank you, is there a way to make isDictionaryKey() generic to work with any passed in dictionary or would you have to create one of these functions for each dictionary you want to use it with?
@DanielGranger See my updated answer.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.