dpaulus
dpaulus

Reputation: 422

Optional keys in different circumstances

Is there a way in Typescript to optionally require keys in different circumstances?

For example

interface IDog {
  name: string;
  weight: number;
}

class Retriever implements IDog {
  name = "Dug";
  weight = 70;

  public updateAttribute(props: IDog) {
    ...
  }
}

Let's say updateAttribute can get either objects: { name: "" }, { weight: 500 }, { name: "", weight: 30 }, {}. Right now, I would either have to add ? to every attribute in IDog, or add ? to (props?: IDog). Both aren't great solutions, because I want Retriever to require the attributes, and I want each attribute to be option in updateAttribute.

Is there a way to update updateAttribute to allow for any permutation of IDog, but still make all attributes required for the class?

Upvotes: 1

Views: 97

Answers (2)

Aluan Haddad
Aluan Haddad

Reputation: 31873

CRice's answer is correct. Using the Partial<T> type is the correct and idomatic approach.

However, it is worth noting that, as TypeScript is a structurally typed language, the implements clause is a formality that does not affect type compatibility. This means that for an interface

interface IDog {
  name: string;
  weight: number;
}

the following are equivalent:

class Dog implements IDog {
  name = "Fred";
  weight = 100;
}

and

class Dog {
  name = "Fred";
  weight = 100;
}

both are implementations of IDog by definition.

It is further worth noting that, by implication, T is an implementation of Partial<T>.

Hence you could write the following as well (--strictNullChecks assumed)

class Retriever {
  name = "Dug";
  weight = 70;

  updateAttribute(props: Partial<this>) {
    Object.assign(this, props);  
  }
}

We can further generalize this in an interesting way by making updateAttribute return this.

updateAttribute<U extends  Partial<this>>(props: U): this & U {
  return Object.assign(this, props);  
}

which allows the following

class Retriever {
  name = "Dug";
  weight = 70;
  age?: number;

  updateAttribute<U extends Partial<this>>(props: U): this & U {
    return Object.assign(this, props);
  }
}

new Retriever().age.toFixed(); // error possibly undefined.
new Retriever().updateAttribute({age: 30}).age.toFixed(); // OK

Upvotes: 1

CRice
CRice

Reputation: 32266

Sounds like you're looking for the Partial type. The partial type is a generic type which makes all the fields of it's argument optional. So for your example:

interface IDog {
  name: string;
  weight: number;
}

class Retriever implements IDog {
  name = "Dug";
  weight = 70;

  public updateAttribute(props: Partial<IDog>) {
    ...
  }
}

Should work. For clarity, Partial<IDog> will be equivalent to the type:

Partial<IDog> = {
  name?: string;
  weight?: number;
}

Upvotes: 2

Related Questions