I am trying to come up with a type guard function which takes an object and an array of type guard functions (all with the same object type but different is X
clauses) and guards that the object matches the guard clauses of all the type guard functions:
type HasFirstName = { firstName: string; };
type HasLastName = { lastName: string; };
const validateHasFirstName = (object: object): object is HasFirstName => 'firstName' in object;
const validateHasLastName = (object: object): object is HasLastName => 'lastName' in object;
const test = {};
if (validateHasFirstName(test)) {
// This works!
test.firstName;
}
if (validateHasLastName(test)) {
// This works!
test.lastName;
}
const validators = [validateHasFirstName, validateHasLastName];
if (validators.every(validator => validator(test))) {
// Neither `test.firstName` nor `test.lastName` work here
}
// I am trying to come up with something like this
function universalValidator(object: object, ...validators) {
return validators.every(validator => validator(object));
}
if (universalValidator(test)) {
// `test.firstName` and `test.lastName` should work here
}
So far I have found a way to convert the type guard function union into an intersected type guard function:
// https://stackoverflow.com/a/50375286/2715716
type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (k: infer I) => void ? I : never;
// This has type `((object: object) => object is HasFirstName) | ((object: object) => object is HasLastName)`
type InteresctedValidators = UnionToIntersection<typeof validators>[number];
However using this intersected type guard function doesn't work as I expected it would:
// That has an error: Signature '(object: object): boolean' must be a type predicate.
const almostUniversalValidator: InteresctedValidators = function (object) {
return validators.every(validator => validator(object));
}
I figure I need something like ReturnType
but for type guards so that I could build a type that has this signature: (object: object): object is A & B
. So instead of intersection the functions, just take their same argument and intersect the return type guards. But ReturnType
on a type guard function just returns boolean
…
I have foun Typing composite type guards - infering/inheriting properties from type guard to its caller but I wasn't able to get that to work off the provided playground. It seems relevant but I don't know how to make it work.
I have also found Dynamic type guard functions but that takes a different form of input - an array of fields the type guard should check for. I have an array of type guard functions whose type guards I want to combine into on.
What can I write to make this work?
if (universalValidator(object, validateHasFirstName, validateHasLastName, …)) {
object.firstName;
object.lastName;
object.…;
}