Within our codebase, we have a utility that generates a typeguard to narrow down a discriminated union:
export type ExtractBranchFromUnion<
UNION,
DISCRIMINANT extends keyof UNION,
BRANCH extends UNION[DISCRIMINANT],
> = UNION extends Record<DISCRIMINANT, BRANCH> ? UNION : never;
export function narrow<
UNION,
DISCRIMINANT extends keyof UNION,
BRANCH extends UNION[DISCRIMINANT],
>(
discriminate: DISCRIMINANT,
branch: BRANCH,
): (
item: UNION,
) => item is ExtractBranchFromUnion<UNION, DISCRIMINANT, BRANCH> {
return (item): item is ExtractBranchFromUnion<UNION, DISCRIMINANT, BRANCH> =>
item[discriminate] === branch;
}
It can easily be utilized in a filter
function to narrow down an array to a specific union member. However, it is important to remember adding as const
after a string literal branch
argument to prevent incorrect type inference:
type A = {type: 'A'; a: string};
type B = {type: 'B'; b: string};
type SimpleUnion = A | B;
const arr: SimpleUnion[] = [];
// Incorrect: type is SimpleUnion[]
const badListOfBs = arr.filter(narrow('type', 'B'));
// Correct: type is B[]
const goodListOfBs = arr.filter(narrow('type', 'B' as const));
Although using pre-made constants like
const Types = {
'A': 'A',
'B': 'B',
} as const;
// Works fine: type is B[] but requires a pre-made constant
const okayListOfBs = arr.filter(narrow('type', Types.B));
helps mitigate the issue, there is a risk of forgetting as const
with a literal value and causing confusion. Additionally, the as const
syntax may not be visually appealing in code. Is there a way to enhance TypeScript to automatically infer a narrower type when a string literal is provided in narrow
? Alternatively, could an informative error message be triggered?