I'm trying to write a user-defined type guard that tests whether the value it's given has all the properties in a given array.
I'm calling this function hasAll
and it's implementation and usage in Javascript would look like this:
function hasAll(obj, keysToCheck) {
if (!obj) return false;
for (const key of keysToCheck) {
const value = obj[key];
if (value === null) return false;
if (value === undefined) return false;
}
return true;
}
hasAll({ foo: 'test', bar: 5 }, ['foo', 'bar']); // true
hasAll({ foo: 'test', bar: 5 }, ['foo', 'bar', 'baz']); // false
What I'm trying to do now is turn the above function into a type guard. This is what I have so far:
// this _almost_ works π΄
type Nullable<T> = T | null | undefined;
type RemoveNullables<T, K extends keyof T> = {
[P in K]-?: T[P] extends Nullable<infer U> ? U : T[P];
};
function hasAll<T, K extends keyof NonNullable<T>>(
obj: T,
keysToCheck: K[],
): obj is RemoveNullables<NonNullable<T>, K> {
// but i'm getting an error here πππ
if (!obj) return false;
const nonNullableObj = obj as NonNullable<T>;
for (const key of keysToCheck) {
const value = nonNullableObj[key];
if (value === null) return false;
if (value === undefined) return false;
}
return true;
}
export default hasAll;
The error message is:
A type predicate's type must be assignable to its parameter's type.
Type 'RemoveNullables<NonNullable<T>, K>' is not assignable to type 'T'.
I've read this answer with a good explanation however it doesn't really help my case.
I want to explicitly assert that my type T
will conform to RemoveNullables<NonNullable<T>, K>
after it runs through this function. I don't really care whether or not T
is assignable to RemoveNullables<NonNullable<T>, K>
(if that makes sense).
- Am I going about this wrong? Is there a better way to write this type guard?
- If this approach is fine, how can I tell typescript that I don't care if the type guard is "unsafe" per se?
RemoveNullables
type towards this aim? It seems like you have some requirement about nullable properties which you haven't described in the question. β NevanevadaRemoveNullables
is to create a type derived from anyT
where all the properties of T have their "nullable" (null
,undefined
, and?
optional) modifiers removed. So if I have a typetype Foo = { foo?: string | null | undefined }
the result ofRemoveNullables<Foo>
would simply be{ foo: string }
. The playground link might help communicate what I'm trying to achieve better. β Interlocutory