Advanced Type Narrowing Utilities in TypeScript
TypeScript's type system is powerful, but sometimes you need more sophisticated ways to narrow down types based on complex conditions. This challenge asks you to create reusable type narrowing utility functions that go beyond the standard typeof, instanceof, and discriminated unions. Building these utilities will deepen your understanding of TypeScript's type system and allow you to write more robust and maintainable code.
Problem Description
You are tasked with creating three TypeScript utility types that perform advanced type narrowing. These utilities should take a type as input and return a narrowed type based on specific criteria.
-
IsStringLike(T): This utility should take a typeTand return a type that representsTif it's a string or a type that can be reasonably treated as a string (e.g.,string[],numberwhich can be converted to a string). IfTis not string-like, it should returnnever. -
ExtractNumericProperties<T>: This utility should take a typeT(an object type) and return a type consisting only of the properties ofTwhose values are numbers. The keys of the resulting type should be the same as the keys of the original type. IfTis not an object type, returnnever. -
FilterByType<T, U>: This utility should take two types,T(an object type) andU(a type constructor, likestringornumber). It should return a type that consists of only the properties ofTwhose values are assignable toU. The keys of the resulting type should be the same as the keys of the original type. IfTis not an object type, returnnever.
Examples
Example 1: IsStringLike
Input: string
Output: string
Explanation: `string` is a string, so it's string-like.
Input: number
Output: never
Explanation: `number` is not string-like.
Input: string[]
Output: string[]
Explanation: `string[]` can be reasonably treated as a string.
Input: { name: string, age: number }
Output: never
Explanation: An object is not string-like.
Example 2: ExtractNumericProperties
Input: { name: string, age: number, count: number }
Output: { age: number, count: number }
Explanation: Only `age` and `count` are numeric properties.
Input: { name: string, age: string }
Output: {}
Explanation: No properties are numeric.
Input: number
Output: never
Explanation: `number` is not an object type.
Example 3: FilterByType
Input: T = { name: string, age: number, count: number }, U = string
Output: { name: string }
Explanation: Only `name` is assignable to `string`.
Input: T = { name: string, age: number, count: number }, U = number
Output: { age: number, count: number }
Explanation: Only `age` and `count` are assignable to `number`.
Input: T = { name: string, age: number, count: number }, U = boolean
Output: {}
Explanation: No properties are assignable to `boolean`.
Input: T = number, U = string
Output: never
Explanation: `number` is not an object type.
Constraints
- All utility types must be implemented using TypeScript's type system features (conditional types, mapped types, etc.).
- The utilities should be as generic as possible to handle a wide range of input types.
- The code should be well-formatted and easy to understand.
- The solutions should be type-safe and avoid any runtime errors.
Notes
- Consider using conditional types to check the type of the input.
- Mapped types can be helpful for iterating over properties of an object type.
typeofandkeyofoperators can be useful for extracting information about types.- Think about how to handle edge cases, such as when the input type is
neverorany. - For
IsStringLike, consider what types you would reasonably want to treat as strings. Don't try to be exhaustive, but cover common cases. - For
ExtractNumericPropertiesandFilterByType, remember that the keys of the output type should match the keys of the input type.