Typescript conditional return interface based on input enums

588 views Asked by At

Requirement is to provide an array of enums as an argument to define the return type interface. Based on these enum attributes Key.A and or Key.B the return type interface should contain A1 and or B1 with attribute keys (like enum keys) A and or B.

Pre-Setup looks like the following:

import { parse } from 'someWhere'; \\ will capitalize config first-lvl attributes

enum Key {
  A = "a",
  B = "b",
}

interface A1 {
  x: string;
}

interface B1 {
  y: string;
}

type FF = {
  A: A1;
  B: B1;
};

type Conf = <???>???;

const Fn = <E extends Key>(key: E[], config: unknown): Conf<E> => {
  return parse.JSON(key, config) as Conf<E>;
};

Outcome should be that Fn is called with some enum values and the returned interface only contains the corresponding attributes like:

const config = '{ "b": { "y": "some string" } }';

const res = Fn([Key.B], config);

console.log(res.B.y) // 'some string'
console.log(res.A.x) // Type Error: Property 'A' does not exist on type

based on that I tried the following:

type Conf<E extends Key> = {
  [K in keyof typeof Key]: K extends E[keyof E] ? FF[K] : never;
};

const res = Fn([Key.B]);

With this implementation all attributes of Key exist in res:

Return Interface

The second approach was to define Conf like

type Conf<E extends Key> = {
  [K in E]: K extends E[keyof E] ? FF : never;
};

Only attribute b exists in return interface but I couldn't find out how to index FF to select the correct interface based on the enum => B1. Additionally the resulting interface is res.b. rather than res.B.

Return Interface

1

There are 1 answers

1
Linda Paiste On BEST ANSWER

This is really tricky because Key.B, "B" and "b" are all different.

If you are willing to rewrite your map FF such that it is keyed by the actual enum values "a" and "b" rather than the enum property names, it becomes trivially simple. You can just use Pick<FF, E> to get the return type.

type FF = {
  a: A1;
  b: B1;
};

const fn = <E extends Key>(key: E[], config?: unknown): Pick<FF, E> => {
  return parse.JSON(key, config) as Pick<FF, E>;
};

const res1: {b: B1} = fn([Key.B]);
const res2: {a: A1} = fn([Key.A]);
const res3: {a: A1, b: B1} = fn([Key.A, Key.B]);

Otherwise I can't quite make sense of it either.