When working with TypeScript, you can utilize the array method filter() to narrow down the type of the resulting array using a specific call signature:
interface ReadonlyArray<T> {
filter<S extends T>(
predicate: (value: T, index: number, array: readonly T[]) => value is S,
thisArg?: any
): S[];
}
This requires the use of a custom type guard function as the `predicate`, ensuring it returns a type predicate.
However, TypeScript does not automatically infer type predicates from function implementations. Even simple cases like `x => typeof x === "string"` are not supported yet, as indicated in microsoft/TypeScript#38390.
For scenarios where direct calls such as
({values})=>values.includes("a")
are used, even established array methods like
includes()
do not act as type guards. An issue requesting this feature has been closed due to the complexity involved (
microsoft/TypeScript#31018).
To work around this limitation, you may need to write explicit custom type guard functions and incorporate them sensibly when calling filter()
. One approach involves generating a type guard function for a broader case:
const objPropHasArrayContainingStringLiteral = <K extends string, T extends string>(propName: K, stringLiteral: T) => <U extends Record<K, readonly string[]>>(obj: U): obj is (
U extends Record<K, readonly (infer V extends string)[]> ?
[T] extends [V] ? U : never : never
) => obj[propName].includes(stringLiteral);
By using
objPropHasArrayContainingStringLiteral(propName, stringLiteral)
, you create a type guard function that verifies if an object contains an array property at
propName
with the specified
stringLiteral
.
The implementation employs a distributive conditional type to filter a union type U
based on a property match criterion.
In practice, utilizing the generated type guard function may resemble something like
objPropHasArrayContainingStringLiteral("values", "a")
, enabling you to filter arrays accordingly:
const objsWithA = foo.filter(objPropHasArrayContainingStringLiteral("values", "a"));
While powerful, it's essential to remember that TypeScript doesn't verify custom type guard function logic. Therefore, caution must be exercised to ensure accurate filtering results.