Created
November 20, 2020 01:59
-
-
Save ArcticZeroo/95e9fd1b4eb0fa51b1d9f84ebb92bf2d to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* This file contains type guards that allow narrowing of types from an unknown type to a known type. | |
* see https://www.typescriptlang.org/docs/handbook/advanced-types.html#user-defined-type-guards for more info | |
*/ | |
// Represents valid keys for JavaScript objects. | |
export type RecordKey = string | number | symbol; | |
// Represents any JavaScript object. | |
export type RecordObject = Record<RecordKey, unknown>; | |
export type TypeofResult = 'string' | 'number' | 'object' | 'boolean' | 'symbol' | 'function' | 'bigint' | 'undefined'; | |
/** | |
* Given a value and a map of expected prop types for each key in the value, return whether all prop types match the | |
* expected type. | |
* @param value - The value, which must first be guarded via isObject | |
* @param expectedPropTypes - A map of <key, expected typeof result string> for your interface | |
*/ | |
function propTypesMatch(value: Record<RecordKey, unknown>, expectedPropTypes: Record<RecordKey, TypeofResult>) { | |
return Object.keys(value).every(key => typeof value[key] === expectedPropTypes[key]); | |
} | |
/** | |
* Type guard for a standard JS object (in TypeScript, this is a Record<RecordKey, unknown> | |
* @param value - The value to guard | |
*/ | |
export function isObject(value: unknown): value is RecordObject { | |
return value != null && typeof value === 'object'; | |
} | |
/** | |
* Type guard for an arbitrary interface T, given a map of its expected prop types for each key. | |
* "duck interface" refers to the property of dynamically typed languages called duck typing, which allows any | |
* two types T and U to be assignable to one another as long as the their signatures are identical -- i.e. their keys | |
* have the correct types, functions have the correct argument/return types, etc. | |
* @param value - The value to guard | |
* @param expectedPropTypes - A map of <key, expected typeof result string> for your interface | |
*/ | |
export function isDuckInterface<T>(value: unknown, expectedPropTypes: { [K in keyof T]?: TypeofResult }): value is T { | |
return isObject(value) | |
&& propTypesMatch(value, expectedPropTypes as Record<RecordKey, TypeofResult>); | |
} | |
/** | |
* Type guard for IUser interface | |
* @param value - The value to guard | |
*/ | |
export function isUser(value: unknown): value is User { | |
return isDuckInterface<User>(value, { | |
id: 'number', | |
email: 'string', | |
googleId: 'string', | |
}); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment