Given the classic discriminated union:
interface Square {
kind: 'square';
width: number;
}
interface Circle {
kind: 'circle';
radius: number;
}
interface Center {
kind: 'center';
}
type Shape = Square | Circle | Center;
How can I type the following filter so that the return type is narrowed down to all Shape
that have a kind
in kinds
.
function filterShape(kinds: Array<Shape["type"]>, shapes: Array<Shape>): Array<?> {
return shapes.filter((shape) => kinds.includes(shape.kind))
}
I tried this, but the return type (unsurprisingly) always ends up being Array<Shape>
.
function filterShape<S extends Shape>(kinds: Array<S["kind"]>, shapes: Array<Shape>): Array<S> {
for instance the type of s
in the call below is Array<Shape>
. I would like Array<Center | Circle>
.
const s = filterShape(["center", "circle"], shapes)
You'll find a TS playground with the above code at https://www.typescriptlang.org/play?#code/JYOwLgpgTgZghgYwgAgMoEcCucooN4BQyxyA1qACYBcyA5AM5Y4S0DcBAvgQaJLIigDCwKAgA2 IiXIhqdBCPEt2XHuGjwkyQRHVRkhEmUo1aSPW07cCYAJ4AHFKgAWcR8gC8aJrmQAfbUUJf21dPnYCGEwQBDBgAHsQZBhgMT4XNwgAHlRkCAAPSFl6NFdHAD4AChkKehoAQSgoOFscgG0AIhqOgF1ygBpkejKIOuRG5taMioBKBqaWnPKDKVwwTCgk4cz6ADoUtOhKyu3HGc9lmr3QcUwKUZOR3ZqZ87gSicXUcpVuBET6GAhiMxp8piNll42ngajQOoxsLgOhxBjCTMgOgpRBJkajYRjzHxcQZ8ZiwtBkT0Iv8QIChp5kql0iNKp1CRTBpighBeoNTqMZkA
CodePudding user response:
This is possible using Extract
:
function filterShape<Kinds extends Shape["kind"][]>(kinds: Kinds, shapes: Array<Shape>): Array<Extract<Shape, { kind: Kinds[number] }>> {
Extract all members of the union Shape
that are assignable to type { kind: Kinds[number] }
.
Kinds
is an array of kinds of shapes we want. So Kinds[number]
gives us a union of the kinds we want.