Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Evaluate templated configuration in a loop

Tags:

typescript

I have a system config (provided by me) that describes what a user config (provided by the user) is capable of.

Then I want to pass each piece of the user config and evaluate it according to the system setup.

To show what I mean, here is some sample code. It should output 2 lines:

  • 123
  • "whatever"
const systemConfig = {
  a: (arg: number) => arg,
  b: (arg: string) => arg,
}

type InferConfig<T extends {}> = { [K in keyof T]: T[K] extends (arg: infer U) => any ? U : never }

const userConfig: InferConfig<typeof systemConfig> = {
  a: 123,
  b: 'whateva',
};

// (everything above working as expected)

for (const key in userConfig) {
  if (key in systemConfig) {
    console.log(systemConfig[key](userConfig[key])); // correct output but highlights errors

    console.log(systemConfig[key as keyof typeof systemConfig](userConfig[key as keyof typeof userConfig])); // luckily doesn't work because I want to avoid "as" if possible
  }
}

Playground

Another requirement is that the loop (not working section of the code) must not include logic in the form of "if (key === 'a') ..." (i.e. depend of a)

like image 563
Page not found Avatar asked Oct 18 '25 19:10

Page not found


2 Answers

You code does not contain the index signature for the type of Object: systemConfig. Which means any attempt of using string to index systemConfig will be prevented. For example we can define the Object with an index signature this way:

const systemConfig: {[key: string]: (arg: any) => string | number} = {
  a: (arg: number) => arg,
  b: (arg: string) => arg,
}

In the above code, [key: string] means string keys can be used to assign values to properties of systemConfig. And (arg: any) => string | number means the value should be a function that takes an argument of any type and returns either string or number (you can modify this)

Once done, you can remove this extra line of code and check it (playground):

console.log(systemConfig[key as keyof typeof systemConfig](userConfig[key as keyof typeof userConfig]));

For your reference: What does a TypeScript index signature actually mean?

like image 200
Jayanika Chandrapriya Avatar answered Oct 20 '25 09:10

Jayanika Chandrapriya


Usually we first define the config type and then extend the transformer.

But if you do want it, see below code.

Playground

type ConfigTransformer = {
  [K in string]: (arg: any) => any
}

type ConfigInput<T extends ConfigTransformer> = {
  [K in keyof T]: T[K] extends (arg: infer U) => any ? U : never
}

type ConfigOutput<T extends ConfigTransformer> = {
  [K in keyof T]: T[K] extends (arg: any) => infer U ? U : never
}

const systemConfig = {
  a: (arg: number) => arg,
  b: (arg: string) => arg,
}

type SystemInput = ConfigInput<typeof systemConfig>
type SystemOutput = ConfigOutput<typeof systemConfig>

const userConfig: SystemInput = {
  a: 123,
  b: 'whateva',
};

function transform<T extends ConfigTransformer>(transformer: T, input: ConfigInput<T>){
  // you must do this `as` because typescript not infer the correct key type
  const keys = Object.keys(transformer) as (keyof T)[];
  for(const key of keys){
    console.log(transformer[key](input[key]))
  }
}

transform(systemConfig, userConfig);
like image 24
Dean Xu Avatar answered Oct 20 '25 08:10

Dean Xu