Exclude entire interface based on inner property

I’m developing a frontend application and our backend generates an SDK that the frontend can use for typings/service calls/etc. One of the main objects that the frontend uses is causing me a few headaches in regards to proper typings. Here are very simplified example types generated by the SDK…

export enum ModelTypes {
  ModelOne = 'ModelOne',
  ModelTwo = 'ModelTwo'
}

export interface ModelOne {
  score: ModelOneScores;
  metadata: ModelOneMetadata;
  modelOneProp_A: string;
  modelOneProp_B: string;
}

export interface ModelTwo {
  modelTwoProp_A: number;
  modelTwoProp_B: number;
}

export interface Model {
  id: string;
  created: Date;
  updated: Date;
  data: (ModelOne | ModelTwo);
  type: ModelTypes;
}

As you can see, ModelOne has the "score" and "metadata" properties, while ModelTwo does not. I have a function to retrieve a score from any "Model" like so (you can assume that inside ModelOneScores or any other ModelNScores interface is an "averageScore" property):

export function getModelScore(model: Sdk.Model): number {
  switch (model.type) {
    case Sdk.ModelTypes.ModelOne:
      return model.data.score.averageScore;
  }
}

Obviously, TypeScript complains because score doesn’t exist on ModelTwo. What is a type I can use to tell TypeScript "I will never pass a ModelTwo to this function so score will always be available on Sdk.Model"?

Note: there are more Models than just ModelTwo that don’t have score, so preferably a catch-all type to exclude any models without the score property would be a nice-to-have.

>Solution :

You could define a type for these Model instances that will always have ModelOne for data instead of ModelTwo:

export type ModelWithModelOne = Exclude<Model, "data"> & {data: ModelOne};

The Exclude there isn’t strictly necessary (I kind of like it for clarity of intent), you could just do:

export type ModelWithModelOne = Exclude<Model, "data"> & {data: ModelOne};

Either way, then you’d use that in the function parameter.

Playground link

If this is completely a one-off, you could just do that with the parameter’s type directly (here I’ve left out Exclude):

export function getModelScore(model: Sdk.Model & {data: ModelOne}): number {
    // ...
}

Playground link

Leave a Reply