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.
type Property = { measurements: Measurement[]}type Measurement = { type: MeasurementType value: number unit: MeasurementUnit}type MeasurementType = | "LOT_DEPTH" | "LOT_AREA" | "LIVING_AREA"type MeasurementUnit = | "SQUARE_FEET" | "SQUARE_METERS" | "FEET" | "METERS"function getProperty(input): Property { return { 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, }, ].filter((measurement) => measurement.value), }}
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, ), }}