Home > Mobile >  How can I allow for an invalid key?
How can I allow for an invalid key?

Time:11-26

I've got a map:

const dataMap: {
    KEY01: { label: 'some string', details: {....}},
    KEY02: { label: 'some other string', details: {...}},
}

const copy = {
 title: dataMap[keyVariable].label,
 info: dataMap[keyVariable].details,
}

so the keyVariable comes from a 3rd party package (with its own types). There are more possible keys 'KEY03', 'KEY04', ..., etc.

Now I am 100% that in my app only KEY01 and KEY02 are possible. The problem is that TS complains that the other possible keyVariable options are not included in my dataMap.

Property 'KEY03' does not exist on type { here insert the shape of my dataMap }

I'd rather not do ts-ignore or ts-expect-error. I could add the missing possible keyVariables (KEY03, KEY05) to my dataMap but that's pointless and would include lots of unnecessary code. Is there a way (! - but I don't know where to put it) to tell TS, that I know the keyVariable can have many more values (other than 'KEY01' and 'KEY02') but I know they are not possible in this context?

CodePudding user response:

This is where you're at currently:

TS Playground

declare const keyVariable: 'KEY01' | 'KEY02' | 'KEY03' | 'KEY04' /* etc. */;

const dataMap = {
  KEY01: { label: 'some string', details: {someKey: 'some value'}},
  KEY02: { label: 'some other string', details: {someKey: 'some value'}},
};

const copy = {
  title: dataMap[keyVariable].label, /*
                 ~~~~~~~~~~~
  Property 'KEY03' does not exist on type... */
  info: dataMap[keyVariable].details, /*
                ~~~~~~~~~~~
  Property 'KEY03' does not exist on type... */
};

In order to fix the compiler diagnostic errors, you could take one of multiple approaches.

The way that is the most type safe (including at runtime) is to actually validate and ensure that the keyVariable value is one of the values that you expect. You can do that using a type guard function:

TS Playground

declare const keyVariable: 'KEY01' | 'KEY02' | 'KEY03' | 'KEY04' /* etc. */;

const dataMap = {
  KEY01: { label: 'some string', details: {someKey: 'some value'}},
  KEY02: { label: 'some other string', details: {someKey: 'some value'}},
};

function isDataMapKey (value: unknown): value is keyof typeof dataMap {
  return value as keyof typeof dataMap in dataMap;
}

if (isDataMapKey(keyVariable)) {
  const copy = {
    title: dataMap[keyVariable].label, // OK
                 //^? const keyVariable: "KEY01" | "KEY02"
    info: dataMap[keyVariable].details, // OK
                //^? const keyVariable: "KEY01" | "KEY02"
  };
}
else {
  // Handle the case that your assumption is wrong, for example:
  throw new Error('Oops, I was wrong');
}

The less safe approach is to use a type assertion. This is not type-safe, but is a way to tell the compiler "shhh... I know more than you do here — believe me". This has no runtime cost, but comes at the expense of incorrectness at runtime if your assumptions turn out to be wrong — in that case, you'll have an actual runtime bug. This is how you could use a type assertion:

TS Playground

declare const keyVariable: 'KEY01' | 'KEY02' | 'KEY03' | 'KEY04' /* etc. */;

const dataMap = {
  KEY01: { label: 'some string', details: {someKey: 'some value'}},
  KEY02: { label: 'some other string', details: {someKey: 'some value'}},
};

const copy = {
  title: dataMap[keyVariable as keyof typeof dataMap].label, // OK
  info: dataMap[keyVariable as keyof typeof dataMap].details, // OK
};

In my own code, I choose to go with the first approach 99.9% of the time: having my program spend a few extra nanoseconds to be sure that my expectations are correct is a negligible price to pay compared to the alternative.


See also: assertion functions and the type system

CodePudding user response:

I agree with everything jsejcksn said. A couple other ideas for handling it:

function assertIsValidKey(key: string): asserts key is keyof typeof dataMap {
  if (!(key in dataMap)) throw new Error('Invalid key');
}

assertIsValidKey(keyVariable);
dataMap[keyVariable].label;

Or something like this:

function castValidKey(key: string): keyof typeof dataMap {
  if (!(key in dataMap)) throw new Error('Invalid key');
  return key as keyof typeof dataMap;
}

dataMap[castValidKey(keyVariable)].label;
  • Related