Reputation: 26125
I have a function that returns whether an object property is undefined
. I need this function instead of just doing obj[key] === undefined
because otherwise I'd sometimes get Property 'foo' does not exist on type 'Bar'.
. It's straightforward to write the type when the property key is a literal. I.e.:
function hasDefinedProp<
Obj extends Partial<Record<string, any>>,
Prop extends string,
>(
obj: Obj,
prop: Prop,
): obj is Obj & Record<Prop, Prop extends keyof Obj ? Exclude<Obj[Prop], undefined> : unknown> {
return obj[prop] !== undefined;
}
const obj: Partial<Record<string, number>> = {};
if (hasDefinedProp(obj, 'foo')) {
obj.foo + 1; // No error.
obj.bar + 1; // "Object is possibly 'undefined'."
}
However, this doesn't work when the key's type is a wide type, i.e.:
const obj: Partial<Record<string, number>> = {};
const key: string = '';
if (hasDefinedProp(obj, key)) {
obj[key] + 1; // No error.
obj.bar + 1; // No error. Should be "Object is possibly 'undefined'."
}
Is it possible to make the type guard work for wide types?
Upvotes: 5
Views: 3952
Reputation: 33071
AFAIK, it is not possible. Once you have added explicit string
type to const key: string = '';
- TS is unable to narrow literal type of key
. As a result, you are allowed to use any string you want to access a property. TS is unable to distinguish two types with type string
in this example:
const obj: Partial<Record<string, number>> = {};
const key: string = '';
if (hasDefinedProp(obj, key)) {
obj[key] + 1; // No error.
obj.bar + 1; // No error. Should be "Object is possibly 'undefined'."
}
This remind me an example with explicit type and immutable type assertion:
const record: Record<string, number> = {
a: 1
} as const;
type Keys = keyof typeof record // string instead of "a"
It means that in this particular case it might be a good idea to forbid using wide types:
type ForbidWide<Prop> = Prop extends string ? string extends Prop ? never : Prop : never
function hasDefinedProp<
Obj extends Partial<Record<string, any>>,
Prop extends string,
>(
obj: Obj,
prop: ForbidWide<Prop>,
): obj is Obj & Record<Prop, Prop extends keyof Obj ? Exclude<Obj[Prop], undefined> : unknown> {
return obj[prop] !== undefined;
}
const obj: Partial<Record<string, number>> = {};
const key: string = '';
hasDefinedProp(obj, key) // error
Upvotes: 3