Background
I'm making type checking function with typescript.
const checkType = <T>(value: unknown, isTypeFn: (value: unknown) => value is T): T => {
if (!isTypeFn(value)) {
console.error(`${isTypeFn} ${value} does not have correct type`);
throw new Error(`${value} does not have correct type`);
}
return value;
};
const isBoolean = (value: unknown): value is boolean => typeof value === 'boolean';
const isString = (value: unknown): value is string => typeof value === 'string';
const isNumber = (value: unknown): value is number => typeof value === 'number';
const isNull = (value: unknown): value is null => value === null;
And I can use it like below.
const a = await getNumber() // this should be number
const numA: number = checkType(a, isNumber); // numA is number or throw Error!
Problem
I want to extend the checkType function like below.
const b = await getNumberOrString();
const bNumOrString: number | string = checkType(b, isNumber, isString);
const bNumOrBoolean: number | boolean = checkType(b, isNumber, isBoolean);
const bStringOrNull: string | null = checkType(b, isString, isNull);
How to improve the checkType for it to work like this ?
CodePudding user response:
The function checkType
will need a rest parameter. Let's call it isTypeFns
. isTypeFns
is a generic type parameter T
which will be an array of functions with type predicates.
const checkType = <
T extends ((value: unknown) => value is any)[]
>(value: unknown, ...isTypeFns: T): CheckType<T> => {
if (isTypeFns.some(fn => fn(value))) {
console.error(`${value} does not have correct type`);
throw new Error(`${value} does not have correct type`);
}
return value as CheckType<T>;
};
The implementation is straight forward. You just need to check if one of the functions in isTypeFns
returns true
given the value
.
The return type gets trickier again. We need to take T
and infer the union of type predicate types.
type CheckType<T extends ((value: unknown) => value is any)[]> =
T[number] extends ((value: unknown) => value is infer U)
? U
: never
We use this for the return type of the function. TypeScript does not understand this complex type when it comes to the return
statement of the implementation. That's why I added an assertion there.
const b = "b" as number | string;
const bNumOrString = checkType(b, isNumber, isString);
// ^? const bNumOrString: string | number
const bNumOrBoolean = checkType(b, isNumber, isBoolean);
// ^? const bNumOrBoolean: number | boolean
const bStringOrNull = checkType(b, isString, isNull);
// ^? const bStringOrNull: string | null