noamyg
noamyg

Reputation: 3094

Mapping types - automatically remove unspecified properties in type

I'm trying to find the proper and straitforward way to map a complex object into a simpler one.

I wanted to utilize something like this:

interface TransferableResponse<T> {
  toServiceResponse(): T;
}

export interface SimpleUserObject {
  accessToken: string;
  refreshToken: string;
  expiresIn: number;
  refreshTokenExpiresIn: number;
  roleIds: number[];
}

export class ComplexUserObject implements TransferableResponse<SimpleUserObject> {
  accessToken: string;
  refreshToken: string;
  expiresIn: number;
  refreshTokenExpiresIn: number;
  someRedundancy: Guid;
  someWeirdStuff: string;
  complexRoles: [{id: number, name: string}];
  
  constructor(init?: Partial<ComplexUserObject>) {
    Object.assign(this, init);
  }

  toServiceResponse(): SimpleUserObject {
    return {
      ...this,
      roleIds: this.complexRoles.map(role => role.id)
    };
  }
}

So in that case I'm creating ComplexUserObject but then, when calling toServiceResponse() I would expect to get only the properties specified in SimpleUserObject . In reality, I'm getting a combination of both (roleIds, but also complexRoles, someWeiredStuff and someRedundancy).

Long story short, is there a simple, one-line way of saying ...this but eliminating stuff that are not included in a type? Pseudo: (...this as SimpleUserObject)

Upvotes: 0

Views: 53

Answers (1)

nate-kumar
nate-kumar

Reputation: 1771

You will unfortunately always need to manually specify the keys from ComplexUserObject you wish to apply to SimpleUserObject in some way.

An interface does not exist at runtime, so your code needs to be valid JavaScript when the interfaces are stripped away. There is no JavaScript way of telling the method "take this subset of keys" without explicitly listing those keys somewhere, i.e. manually as keys on the new object, as an array which is iterated, as a reference object which is iterated etc

Your simplest bet is just to list out the new keys

toServiceResponse(): SimpleUserObject {
  return {
    accessToken: this.accessToken,
    refreshToken: this.refreshToken,
    expiresIn: this.expiresIn,
    refreshTokenExpiresIn: this.refreshTokenExpiresIn,
    roleIds: this.complexRoles.map(role => role.id)
  };
}

You could also use a class instead of an interface (which would allow the resulting concrete instance to become a typed entity even in JavaScript), but will still suffer the necessary manual listing of keys to instantiate the class, just this time in a constructor instead of directly on the returned object

export class SimpleUserClass {
  constructor(public args: SimpleUserObject) {}
}
toServiceResponse(): SimpleUserClass {
  return new SimpleUserClass({
    accessToken: this.accessToken,
    refreshToken: this.refreshToken,
    expiresIn: this.expiresIn,
    refreshTokenExpiresIn: this.refreshTokenExpiresIn,
    roleIds: this.complexRoles.map(role => role.id)
  })
}

Upvotes: 1

Related Questions