Home > Back-end >  Type Guard function with multiple dynamic types
Type Guard function with multiple dynamic types

Time:01-16

I would like to create a type guard function which checks for multiple types. It should be a mix of typeof operator and instanceof operator.

I started with the following implementation:

function typeof(obj: any, type: "string"): obj is string;
function typeof(obj: any, type: "number"): obj is number;

And I am using it the following way:

function do(value: any) {

  let str: string = "";
  let num: number = 0;

  if(typeof(value, "string")){
      str = value;
  }
  if(typeof(value, "number")){
      num = value;
  }
}

The function should also check against objects e.g. Date:

let date: Date = null;
if(typeof(value, Date)){
    date = value;
}

I tried:

function typeof<K>(obj: any, type: K): obj is K;

But Typescript only show DateContructor if type is Date.

My final goal is a function which can check multiple types:

function typeof(obj, ...types:[]): ???

let date: Date = new Date();
//if(typeof value === "number" || value instanceof Date)
if(typeof(value, "number", Date)){
  date = date   value;
}

How should the typeOf function should be defined?

CodePudding user response:

For the strings to work, we need a predefined map of strings to their respective types first:

type TypeofMap = {
    string: string;
    number: number;
    boolean: boolean;
    symbol: symbol;
    undefined: undefined;
    object: object;
    function: (...args: any[]) => any;
    bigint: bigint;
};

Then we can lookup the correct type later:

type ToType<T> =
    T extends keyof TypeofMap
        ? TypeofMap[T]
        : T extends new (...args: any[]) => infer R
            ? R
            : T;

I've also made it so that if T is a constructor, then the type should be an instance of T. Otherwise, I don't know anything else, so I will use T itself as the type.

Here's the signature for typeOf (note the capitalization on O, you cannot name functions "typeof"):

declare function typeOf<T extends keyof TypeofMap | {} | null | undefined>(value: any, type: T): value is ToType<T>;

The generic constraint looks weird because I wanted the function to autocomplete keys in TypeofMap if you try to pass a string for the type, but it should still allow any type. Since {} | null | undefined represents any possible value, but is different from keyof TypeofMap, I still get autocomplete and the union is not reduced to only {} | null | undefined.

Playground

  • Related