I am looking to filter an array and refine the type based on shared properties within a union type. The elements in the array belong to various types that share some common characteristics.
type TypeA = {name: string; id: string; createdAt: Date};
type TypeB = {name: string; id: string; metaData: Record<string, unknown>};
type TypeC = {id: string};
type TypeD = {name: string; createdAt: Date};
// etc.
type MyUnionType = TypeA | TypeB | TypeC | TypeD;
const hasName = (x: MyUnionType): x is MyUnionType & {name: string} => 'name' in x;
const hasId = (x: MyUnionType): x is MyUnionType & {id: string} => 'id' in x;
function foo(ary: MyUnionType[]) {
ary.filter(hasName)
.filter(hasId)
.map(x => x.name + x.id); // ❌ Error because it thinks property `id` does not exist
}
I have come up with two possible solutions:
- Develop a specific filter for each required combination:
function hasNameAndId(x: MyUnionType): x is MyUnionType & {name: string} {
return 'name' in x && 'id' in x;
}
This approach may not be practical in the long run, as it involves creating multiple functions for different filter combinations.
- Instead of separate filter functions, incorporate the filters directly in the code with the type information:
function foo(ary: MyUnionType[]) {
ary.filter((x): x is MyUnionType & {name: string} => 'name' in x)
.filter((x: MyUnionType & {name: string}): x is MyUnionType & {name: string; id: string} => 'id' in x)
.map(x => x.name + x.id);
}
This alternative can become complicated and hard to maintain.