1
enum Letter {
  A = "A",
  B = "B",
  C = "C"
}

export type EntityT<T extends Letter = Letter> = T extends Letter.A
  ? { a: number }
  : T extends Letter.B
  ? { b: string }
  : T extends Letter.C
  ? { c: string }
  : never;

type DeltaT<T extends EntityT> = _DeltaT<T, keyof T>;

export type _DeltaT<E extends EntityT, K extends keyof E> = {
  key: K;
  oldValue: E[K];
  newValue: E[K];
};

export type ProblemT<T extends Letter = Letter> = EntityT<T> extends infer E
  ? {
      id: string;
      type: T;
      delta: DeltaT<E>[];
      oldEntity: E;
      newEntity: E;
    }
  : never;

const testCase: ProblemT<Letter.A> = {
  id: "id",
  type: Letter.A,
  delta: [{ key: "a", oldValue: 1, newValue: 2 }],
  oldEntity: { a: 1 },
  newEntity: { a: 2 }
};

function myFunc<T extends Letter>(e: ProblemT<T>) {
  switch (e.type) {
    case Letter.A: {
      const uselessConversion = e as ProblemT<Letter.A>;
      return uselessConversion.newEntity.a;
    }
    case Letter.B: {
      const uselessConversion = e as ProblemT<Letter.B>;
      return uselessConversion.newEntity.b;
    }
    case Letter.C: {
      const uselessConversion = e as ProblemT<Letter.C>;
      return uselessConversion.newEntity.c;
    }
  }
}

I want to declare a type ProblemT that depends on his type key, will return different values in oldEntity and newEntity. I want to run myFunc with the switch without the need to convert the object manually, and TypeScript will infer the type based on the type property. But it does not work well. How to make the switch infer the type automatically without the need to convert. Is there a better way to declare the ProblemT differently type so the switch can infer it?

Here is a link to the TypeScript playground

1 Answer 1

4

Note that types such as Letter.A | Letter.B also extends Letter, so the compiler does not know that things are as constrained as they are.

Something like this will work:

enum Letter {
  A = "A",
  B = "B",
  C = "C"
}

export type EntityT<T extends Letter> =
  T extends Letter.A ? { a: number }
  : T extends Letter.B ? { b: string }
  : T extends Letter.C ? { c: string }
  : never;

export type DeltaT<T extends Letter, K = keyof EntityT<T>> = K extends keyof EntityT<T>
  ? {
    key: K;
    oldValue: EntityT<T>[K];
    newValue: EntityT<T>[K];
  }
  : never;

export type ProblemT<T extends Letter> = {
  id: string;
  type: T;
  delta: DeltaT<T>[];
  oldEntity: EntityT<T>;
  newEntity: EntityT<T>;
};

function myFunc(e: ProblemT<Letter.A> | ProblemT<Letter.B> | ProblemT<Letter.C>) {
  switch (e.type) {
    case Letter.A: return e.newEntity.a;
    case Letter.B: return e.newEntity.b;
    case Letter.C: return e.newEntity.c;
  }
}
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.