Home > Back-end >  Implementing a function that infers value type based on the key
Implementing a function that infers value type based on the key

Time:11-30

I have an object type where keys map to different types:

type Value = {
  str: string;
  num: number;
};

And I’m trying to implement a universal format function:

const format = <K extends keyof Value>(key: K, value: Value[K]): string => {
  if (key === 'str') {
    // @ts-expect-error -- Property 'split' does not exist on type 'string | number'
    return value.split('').join('');
  }
  if (key === 'num') {
    // @ts-expect-error -- The left-hand side of an arithmetic operation must be of type 'any', 'number', 'bigint' or an enum type
    return `${value - 1}`;
  }
  return '';
};

But why is TypeScript unable to narrow down value’s type inside an if condition?

I have an alternative working solution, but the function signature is quite inconvenient:

type Value =
  | ['str', string]
  | ['num', number];

const format = (...[key, value]: Value): string => {
  if (key === 'str') {
    return value.split('').join('');
  }
  if (key === 'num') {
    return `${value - 1}`;
  }
  return '';
};

Interactive code on TypeScript playground showing a call to format being written with the key set to "str" and a type hint showing the type of value is string

Full example (playground link):

type Value = {
    str: string;
    num: number;
};

type KVTuple<T> = {
    [P in keyof T]: [key: P, value: T[P]];
}[keyof T];

type X = KVTuple<Value>;

const format = (...[key, value]: KVTuple<Value>): string => {
    if (key === "str") {
        return value.split("").join("");
    }
    if (key === "num") {
        return `${value - 1}`;
    }
    return "";
};

format("str", "text");              // Works
format("num", 1);                   // Works
format("str", 1);                   // Error as desired
format("num", "text");              // Error as desired
format<"str" | "num">("str", 42);   // Error as desired
  • Related