← Back to Guides

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}
4
5type Measurement = {
6 type: MeasurementType
7 value: number
8 unit: MeasurementUnit
9}
10
11type MeasurementType =
12 | "LOT_DEPTH"
13 | "LOT_AREA"
14 | "LIVING_AREA"
15
16type MeasurementUnit =
17 | "SQUARE_FEET"
18 | "SQUARE_METERS"
19 | "FEET"
20 | "METERS"
21
22function 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,
),
}
}

Want news and updates?

Join a handful of people who get my latest content and product updates, directly to your inbox.