Filter an array in Typescript
I had a typed array and wanted to remove elements with an empty value. In vanilla Javascript that's easy enough, but instead of regular strings I was using string unions to restrict the available options.
1type Property = {2 measurements: Measurement[]3}45type Measurement = {6 type: MeasurementType7 value: number8 unit: MeasurementUnit9}1011type MeasurementType =12 | "LOT_DEPTH"13 | "LOT_AREA"14 | "LIVING_AREA"1516type MeasurementUnit =17 | "SQUARE_FEET"18 | "SQUARE_METERS"19 | "FEET"20 | "METERS"2122function getProperty(input): Property {23 return {24 measurements: [25 {26 type: "LOT_DEPTH",27 value: input.lotDepth.value,28 unit: input.lotDepth.unit,29 },30 {31 type: "LOT_AREA",32 value: input.lotArea.value,33 unit: input.lotArea.unit,34 },35 {36 type: "LIVING_AREA",37 value: input.livingArea.value,38 unit: input.livingArea.unit,39 },40 ].filter((measurement) => measurement.value),41 }42}43
Typescript was able to correctly infer the type of the measurements
array, but as soon as I added the filter
it broke. Why? Filter returns a regular array of regular objects, and the type information about the string unions was lost
Type 'string' is not assignable to type 'MeasurementName'
Solutions
The solution I ended up going with was to use type predicates to tell filter which type to use. I found out afterward that TS can infer correctly without the type predicate as long as it's split with an intermediate variable.
Type predicates
.filter((measurement): measurement is Measurement => measurement.value)
Type predicates (JSDoc)
.filter(/** @type function(*): measurement is Measurement */ (measurement) => measurement.value)
Intermediate variable (JSDoc)
function getProperty(input): Property { /** @type {Measurement[]} */ const measurements = [ { type: "LOT_DEPTH", value: input.lotDepth.value, unit: input.lotDepth.unit, }, { type: "LOT_AREA", value: input.lotArea.value, unit: input.lotArea.unit, }, { type: "LIVING_AREA", value: input.livingArea.value, unit: input.livingArea.unit, }, ] return { measurements: measurements.filter( (measurement) => measurement.value, ), }}