Reputation: 1642
In the scenario below, I don't want to trust the untrusted object and want to check that each field is truthy before acting upon it. However, using the Partial generic, it does not infer that the field is not undefined when attempting to pass it to the function. Is there a better way to handle this?
export interface IFoo {
id: number;
value: string;
}
export function DoBar(obj: IFoo) {
// do stuff
return;
}
const untrustedObj: Partial<IFoo> = { id: 1, value: 'test' };
if (untrustedObj.id && untrustedObj.value) {
DoBar(untrustedObj); // Error
}
error:
Argument of type 'Partial<IFoo>' is not assignable to parameter of type 'IFoo'.
Types of property 'id' are incompatible.
Type 'number | undefined' is not assignable to type 'number'.
Type 'undefined' is not assignable to type 'number'.ts(2345)
Upvotes: 0
Views: 233
Reputation: 328097
See microsoft/TypeScript#28882 for why this doesn't work. If an object is not of a discriminated union type, checking its properties will not narrow the type of the object itself. You'll either need a user defined type guard or some other refactoring to make this work.
One such refactoring is to save the properties to their own variables, since TypeScript's compiler will narrow the type of variables if you check them:
const uId = untrustedObj.id;
const uValue = untrustedObj.value;
if (typeof uId !== "undefined" && typeof uValue !== "undefined") {
DoBar({ id: uId, value: uValue }); // okay
}
My suggestion for a type guard function would look like this:
function definedProp<T, K extends keyof T>(
obj: T, ...props: K[]
): obj is T & Required<Pick<T, K>> {
return props.every(k => typeof obj[k] !== "undefined");
}
This will check to see that obj
has a defined value for each key in the props
array, and if so, it will narrow the passed-in obj
. Then you use it like this:
if (definedProp(untrustedObj, "id", "value")) {
DoBar(untrustedObj); // okay
}
Okay, hope that helps; good luck!
Upvotes: 3
Reputation: 51034
You can write a type guard function using is
to narrow the object's type:
function hasKey<T, K extends keyof T>(obj: Partial<T>, key: K):
obj is Partial<T> & { [k in K]: T[k] }
{
return Object.prototype.hasOwnProperty.call(obj, key);
}
Usage:
if(hasKey(untrustedObj, 'id') && hasKey(untrustedObj, 'value')) {
doBar(untrustedObj); // no type error
}
I've used Object.prototype.hasOwnProperty
as a more general solution to testing this, since you might want to check if a boolean property exists (rather than also checking if it's true).
Upvotes: 4