Reed
Reed

Reputation: 1642

How to better handle Partial<T> interfaces

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

Answers (2)

jcalz
jcalz

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!

Link to code

Upvotes: 3

kaya3
kaya3

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).

Playground Link

Upvotes: 4

Related Questions