LizardDog
LizardDog

Reputation: 43

Typescript define object type depending on key

I`m trying to define function with 2 args: (templateKey: T, templateData: ObjectType) => ... where templateKey is a well known mapped value and templateData is data specified for this key. As result i want one function to work with all templates from one place.

I've tried this:

export const Template = [
  'CASE1',
  'CASE2',
] as const;

type RequestDTO = {
  test: string,
}

type RequestDTO2 = {
  jest: string,
}

type ObjectType<T extends TemplateKey> =
  T extends 'CASE1' ? RequestDTO :
  T extends 'CASE2' ? RequestDTO2 :
      never;

export type TemplateKey = typeof Template[number];

const func = async <T extends TemplateKey>({ template, templateData } : { template: T, templateData: ObjectType<T> }) => 1;

const U = func({ template: 'CASE1', templateData: { test: '123' } });

All works fine for me - hints are where they must be and this way pretty convenient. But! I see one awkward thing: no typehints when i write new rule in T extends 'CASE2' ? RequestDTO2 and when number of templates grows, there will be huge ObjectType ruleset with rows of ternary operators without typehitting... But i have no idea how to do it without a map or something like map and im a bit confused. Have you any ideas about how to optimize this or what i did wrong?

Upvotes: 1

Views: 1236

Answers (2)

LizardDog
LizardDog

Reputation: 43

type TemplateDTO = {
  baz: string,
}

type TemplateMeta<T> = {
  bar: value;
} & T

type SomeEventMeta = {
  foo: string,
}

enum Template = {
 SOMEVENT = 'someEvent'
}

type TemplateDTO = {
  [NotificationTemplate.SOMEVENT]: TemplateDTO;
}

type TemplateMetaMap = {
  [NotificationTemplate.SOMEVENT]: TemplateMeta<SomeEventMeta>,
}

type TemplateType = keyof Record<Template, string>;

const create = (template: Template,
  templateData: TemplateDTO[TemplateType],
  meta: TemplateMetaMap[TemplateType]) => {...};

Relying on answers i did it. I wanted to avoid map-types and wanted to use generics, but it will require a lot of such code in project:

notify<typeHere, typeThere>(...) and IMO what in my case is redundant and will make code more complicated and refactoring will take more time against case when 3 maps are placed in one file and all i need is to add new interface and pass it to TemplateMeta, and TemplateMeta contains common values for all templates, somewhere i need it, somewhere not, so its generic. And TemplateDTO hardly binded to Template cos w/o this data Template wont work at all. Thanks everyone, im gonna now try it out on my project.

Upvotes: 0

ghybs
ghybs

Reputation: 53185

You can simply build an interface to map your (Template) keys to their corresponding data type, then list available keys using keyof and request the associated data type:

interface Template {
  CASE1: RequestDTO;
  CASE2: RequestDTO2;
}

function func<T extends keyof Template>({
  template: T,
  templateData: Template[T]
}) {}

func({
  template: "CASE1",
  templateData: {
    test: ""
  },
});

TS Playground

Upvotes: 1

Related Questions