0

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.…;
}

Playground

Tomáš Hübelbauer
  • 9,179
  • 14
  • 63
  • 125
  • 1
    [This?](https://tsplay.dev/wXO71W) In your original playground, you used `universalValidator(object)` without passing in any validators, which I assume is a typo. – kelsny Feb 10 '23 at 20:49
  • 1
    Does this differ from the `intersection` function as described [here](https://stackoverflow.com/a/75416075/2887218)? – jcalz Feb 10 '23 at 21:01
  • @jcalz Oh I missed the other part of that answer completely. I got sidetracked by thinking the first part wasn't it and ended up not reading the rest. Thank you, too! – Tomáš Hübelbauer Feb 10 '23 at 21:05

0 Answers0