Jacob Paris
← Back to all content

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,
),
}
}
Professional headshot

Hi, I'm Jacob

Hey there! I'm a developer, designer, and digital nomad with a background in lean manufacturing.

About once per month, I send an email with new guides, new blog posts, and sneak peeks of what's coming next.

Everyone who subscribes gets access to the source code for this website and every example project for all my tutorials.

Stay up to date with everything I'm working on by entering your email below.

Unsubscribe at any time.