I was wondering if it's possible to create a type guard which checks if every item of an array is defined. I already have a type guard for checking a single value, but having a solution that would do that for a whole array (with would be great.
To put it in code, I'm looking for something that would type guard the following:
// Input array type:
[ Something | undefined, SomethingDifferent | undefined ]
// Type guard resolving to:
[ Something, SomethingDifferent ]
Currently I have the following type guard to check a single item:
function isDefined<T>(value: T | undefined | null): value is T {
return value !== null && typeof value !== 'undefined';
}
The logical implementation of the type guard is quite easy (especially when reusing the guard for a single value):
function isEachItemDefined<T extends Array<unknown>>(value: T): ?? {
return value.every(isDefined);
}
My use case for this would be filtering after using the withLatestFrom
operator (RxJS). I have an edge case where I'd like to filter out the "event" whenever any of the values is not defined.
observable1.pipe(
withLatestFrom(observable2, observable3, observable4), // <-- each returns value or undefined
filter(isEachItemDefined),
map(([ item1, item2, item3, item4 ]) => {
// event if the logic works correctly,
// typescript wouldn't recognise the items as defined
// e.g. item2 is still "string | undefined"
}),
);
CodePudding user response:
function isDefined<T> (value: NonNullable<T> | undefined | null): value is NonNullable<T> {
return value !== null && value !== undefined
}
function isEachItemDefined<T> (value: Array<NonNullable<T> | undefined | null>): value is Array<NonNullable<T>> {
return value.every(isDefined)
}
Here's some code to prove it works:
function isEasyGoing<T>(arr: (T | undefined | null)[]) {
// do stuff
}
function isHighMaintenance<T>(arr: NonNullable<T>[]) {
// do stuff carefully
}
function handle<T>(arr: (NonNullable<T> | undefined | null)[]) {
if (isEachItemDefined(arr)) {
isHighMaintenance(arr) // narrowed to string[], no type check error
} else {
isEasyGoing(arr) // still (string | undefined | null)[]
}
}
const withoutNull: string[] = ['a', 'b', 'c']
const withNull: (string | undefined | null)[] = ['a', 'b', null, 'c']
isHighMaintenance(withoutNull)
isHighMaintenance(withNull) // type check error as expected
handle(withoutNull)
handle(withNull)
type NullableString = string | null
const nullableWithoutNull: (NullableString | undefined | null)[] = ['a', 'b', 'c']
const nullableWithNull: (NullableString | undefined | null)[] = ['a', 'b', null, 'c']
// Since the generic parameter is constrained as NonNullable<T>
// we can't sneak NullableString by as T
isHighMaintenance(nullableWithoutNull) // type check error as expected
isHighMaintenance(nullableWithNull) // type check error as expected
handle<NullableString>(nullableWithoutNull)
handle<NullableString>(nullableWithNull)