Using a constant string value to narrow down a union type is simple and effective:
type Payload1 = { /* ... arbitrary type ... */ };
type Payload2 = { /* ... arbitrary type ... */ };
type T1 = { type: 'type1', payload: Payload1 }
type T2 = { type: 'type2', payload: Payload2 }
type T = T1 | T2;
const fn = (value: T) => {
if (value.type === 'type1') {
value; // TypeScript recognizes `value is T1`
}
if (value.type === 'type2') {
value; // TypeScript recognizes `value is T2`
}
};
Initially, there are only two cases to consider:
value.type
is the constant"type1"
value.type
is the constant"type2"
But when expanding T
to allow payload
to be a single item or an array, the possibilities increase to 4:
value.type
is"type1"
andvalue.payload
is not anarray
value.type
is"type1"
andvalue.payload
is anarray
value.type
is"type2"
andvalue.payload
is not anarray
value.type
is"type2"
andvalue.payload
is anarray
Here's an illustration:
type Payload1 = {};
type Payload2 = {};
type T1Single = { type: 'type1', payload: Payload1 }
type T1Batch = { type: 'type1', payload: Payload1[] };
type T2Single = { type: 'type2', payload: Payload2 }
type T2Batch = { type: 'type2', payload: Payload2[] };
// Updated T with 4 types:
type T = T1Single | T1Batch | T2Single | T2Batch;
const fn = (value: T) => {
if (value.type === 'type1' && !Array.isArray(value.payload)) {
value; // TypeScript indicates `value is T1Single | T1Batch` now
}
if (value.type === 'type1' && Array.isArray(value.payload)) {
value; // TypeScript indicates `value is T1Single | T1Batch` now
}
if (value.type === 'type2' && !Array.isArray(value.payload)) {
value; // TypeScript indicates `value is T2Single | T2Batch` now
}
if (value.type === 'type2' && Array.isArray(value.payload)) {
value; // TypeScript indicates `value is T2Single | T2Batch` now
}
};
Why is typescript only partially narrowing down the type, and how can I achieve fully narrowed values for the 4 cases?
UPDATE: It appears that using multiple conditions in the if
statement does not help; TypeScript struggles with narrowing based on Array.isArray
alone:
type Payload = {};
type Single = { payload: Payload }
type Batch = { payload: Payload[] };
const fn = (value: Single | Batch) => {
if (!Array.isArray(value.payload)) {
value; // TypeScript still shows `value is Single | Batch`
}
if (Array.isArray(value.payload)) {
value; // TypeScript still shows `value is Single | Batch`
}
};