Skip to content

Instantly share code, notes, and snippets.

@JSuder-xx
Last active February 7, 2021 16:09
Show Gist options
  • Save JSuder-xx/ccacaed3306d63f52a41722c24d584af to your computer and use it in GitHub Desktop.
Save JSuder-xx/ccacaed3306d63f52a41722c24d584af to your computer and use it in GitHub Desktop.
Micro Api for building type predicates in TypeScript.
/** Micro Api for building type predicates */
module TypePredicateApi {
export type TypePredicate<refined> = (val: any) => val is refined;
export type TypeOfTypePredicate<refiner> = refiner extends TypePredicate<infer refined> ? refined : never;
export type TypePredicateMap = { [propertyName: string]: TypePredicate<any> }
export type ObjectFromTypePredicateMap<map extends { [propertyName: string]: TypePredicate<any> }> = {
[propertyName in keyof map]: TypeOfTypePredicate<map[propertyName]>;
}
/** Create a predicate which tests for an object. */
export const createObjectPredicate = <typePredicateMap extends TypePredicateMap>(typePredicateMap: typePredicateMap) =>
(obj: any): obj is ObjectFromTypePredicateMap<typePredicateMap> =>
(obj === null) ? false
: (typeof obj !== "object") ? false
: Object.keys(obj).every(propertyName => typePredicateMap[propertyName](obj[propertyName]));
export const isString: TypePredicate<string> = (val: any): val is string => typeof val === "string";
export const isNumber: TypePredicate<number> = (val: any): val is number => typeof val === "number";
export const isBoolean: TypePredicate<boolean> = (val: any): val is boolean => typeof val === "boolean";
export const isArray: TypePredicate<any[]> = (val: any): val is any[] => !!val && typeof val.push === "function";
export const createIsArray = <Item extends unknown>(pred: TypePredicate<Item>): TypePredicate<Item[]> =>
(val: any): val is Item[] =>
isArray(val) && val.every(pred);
export const isValue = <value extends unknown>(value: value) => (val: any): val is value => (val === value);
export const isNull: TypePredicate<null> = (val: any): val is null => val === null;
export const isValue2 = <value1 extends unknown, value2 extends unknown>(value1: value1, value2: value2) =>
(val: any): val is (value1 | value2) =>
(val === value1 || val === value2);
export const isValue3 = <value1 extends unknown, value2 extends unknown, value3 extends unknown>(value1: value1, value2: value2, value3: value3) =>
(val: any): val is (value1 | value2 | value3) =>
(val === value1 || val === value2 || val === value3);
export const isValue4 = <value1 extends unknown, value2 extends unknown, value3 extends unknown, value4 extends unknown>(value1: value1, value2: value2, value3: value3, value4: value4) =>
isValue2(isValue3(value1, value2, value3), value4);
export const or = <left, right>(left: TypePredicate<left>, right: TypePredicate<right>) =>
(val: any): val is (left | right) =>
left(val) || right(val);
export const or3 = <one, two, three>(one: TypePredicate<one>, two: TypePredicate<two>, three: TypePredicate<three>) =>
(val: any): val is (one | two | three) =>
one(val) || two(val) || three(val);
}
module Example {
const isGender = TypePredicateApi.isValue3("male" as const, "female" as const, "unknown" as const);
const isPerson = TypePredicateApi.createObjectPredicate({
firstName: TypePredicateApi.isString,
gender: isGender,
lastName: TypePredicateApi.isString,
ageInYears: TypePredicateApi.isNumber,
isCool: TypePredicateApi.isBoolean
})
// Observe the use of TypeOfTypePredicate to get ahold of the computed type.
type Person = TypePredicateApi.TypeOfTypePredicate<typeof isPerson>;
const bob: Person = {
firstName: "Bob",
lastName: "Smith",
ageInYears: 30,
gender: "male",
isCool: true
}
const isCompany = TypePredicateApi.createObjectPredicate({
ceo: isPerson,
coo: isPerson,
cto: isPerson,
workers: TypePredicateApi.createIsArray(isPerson)
})
export function doSomething(valueFromOutsideWord: any) {
if (isCompany(valueFromOutsideWord)) {
console.log(`CEO Age: ${valueFromOutsideWord.ceo.ageInYears}`);
console.log(`Company has ${valueFromOutsideWord.workers.length}`);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment