Home > Net >  Type guard for verifying that each item of an array is defined
Type guard for verifying that each item of an array is defined

Time:12-12

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)
  • Related