Mapping a dynamic object to typescript function params

662 Views Asked by At

I'm trying to make a list ( example below ) and map it to the parameters of a function. I imagine the arguments being an object with keys that match the names of the params, and the values being the options for each arg (whether it's required or more important, what type ).

interface Command<T> {        // Theese are the parts that I'm curious about, 
  args: T,                    // idk if its even possible, or if there's another solution.
  run(...args: T) {           // 
    
  }
}

const Arguments = {
  name: {
    type: "string",
    required: true,
  },
  duration: {
    type: "number",
    required: false,
  }
};

const cmd: Command<typeof Arguments> = {
  args: Arguments,
  run(name, duration) {
    console.log(`banned ${name} for ${duration ?? "forever"}`);
  }
}

If it is possible or you have any ideas of how to achieve this, please let me know as I've been struggling with this for days now .

2

There are 2 best solutions below

1
On BEST ANSWER

You'll need a little more magic to do this. First a type that describes the type of the arguments object:

type ArgsObject = Record<string, { type: "string" | "number" | "boolean"; required: boolean }>;

Then we'll map strings to their actual types (i.e. "string" to string):

type MapToType = {
    string: string;
    number: number;
    boolean: boolean;
    // more if needed
};

And finally we map over the arguments and get the type for each:

type ArgsToContext<Args extends ArgsObject> = {
    [K in keyof Args]: MapToType[Args[K]["type"]] | IsOptional<Args[K]>;
};

Where IsOptional is defined as:

type IsOptional<T extends ArgsObject[string]> = T["required"] extends true ? never : undefined;

This is because if it is required, it gives us never, and Type | never is always Type. However if it is not required (optional), it gives us undefined, resulting in the desired Type | undefined

Here is a playground showcasing this.

2
On

Since you want to infer the types of your object as it is, we will make use of the as const assertion.

Then we need to create an ObjectToParams<T> type alias which allows us to do this transformation and achieves the desired constraint format for the output object assignable

const Arguments = {
  name: {
    type: "string",
    required: true,
  },
  duration: {
    type: "number",
    required: false,
  }
} as const;


type ObjectToParams<T extends { [k: string]: { type: string } }> 
= { args: T, run: (args: { [k in keyof T]: T[k]["type"] }) => void }



const cmd: ObjectToParams<typeof Arguments> = {
  args: Arguments, // Get inference for Arguments

  run({name, duration}) {
    console.log(`banned ${name} for ${duration ?? "forever"}`);
  }
}

Code Playground