1

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?

2
  • 1
    I'm confused about where you're expecting hints... but if you're mapping string literals to types, plain interfaces do that very well... like this, and then it should be easy to add cases. Does that meet your needs or am I missing something? Commented Jan 28, 2022 at 1:29
  • yes, definetly, but difference is that i using enums for types, and i've found solution Commented Jan 29, 2022 at 15:31

2 Answers 2

1

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

Sign up to request clarification or add additional context in comments.

Comments

0
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.

2 Comments

thanks you guys, ghybs, jcals
whoops, i didnt get what i wanted. Now when i call create, meta is union type like TemplateType<...> | TemplateType<{}>, how i can achieve type when meta is required exactly as in map [type]: data ?

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.