Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

Infer object's values types using Generic

I’ve been stuck on this for a while without being able to find an elegant solution.

I have a Enum:

export enum Enum {
    A,
    B,
    C,
}

Then an object that uses said Enum, the structure is relatively known, as it’s value will always be a function which will return a string, but the unknown thing is that the function may take any amount of args:

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

import { A, B, C } from '../enums/';

export const obj = {
    [A]: () => 'example 1',
    [B]: (arg1: string) => 'example 2, arg1: %s',
    [C]: (nr: string, arg2: string) => 'example %s %s',
}

Last part of the structure is a function, which makes use of obj to retrieve certain strings based on it’s args:

const fct = <E extends Enum>(
    code: E,
    ...args: Parameters<(typeof obj)[E]>
) => {
    ...
}

Everything works, but it is not type-safe, as I require every Enum value to be assigned to a value inside of obj.

If I were to create a basic type, Parameter would not display the proper arguments of the function:

type ObjectType = { [key in Enum]: (...args: any[]) => string }
Parameters<(typeof obj)[A]> // -> (...args: any[])

>Solution :

You can constrain the type of obj to only allow keys of Enum and values of "functions that return string" using the satisfies operator (see also the type utility Record<Keys, Type>), and you can constrain the generic type parameter E in the function fct by the actual keys of obj rather than any Enum (which might or might not be present as a key) like this:

TS Playground

enum Enum {
  A,
  B,
  C,
}

const obj = {
  [Enum.A]: () => 'example 1',
  [Enum.B]: (arg1: string) => 'example 2, arg1: %s',
  [Enum.C]: (nr: string, arg2: string) => 'example %s %s',
} satisfies Record<Enum, (...args: any[]) => string>;

const fct = <E extends keyof typeof obj>(
  code: E,
  ...args: Parameters<(typeof obj)[E]>
) => {};

fct(Enum.A); // Ok
fct(Enum.A, "foo"); /* Error (expected)
            ~~~~~
Expected 1 arguments, but got 2.(2554) */

fct(Enum.B, "foo"); // Ok
fct(Enum.B); /* Error (expected)
~~~~~~~~~~~
Expected 2 arguments, but got 1.(2554) */


fct(Enum.C, "foo", "bar"); // Ok
fct(Enum.C, "foo"); /* Error (expected)
~~~~~~~~~~~~~~~~~~
Expected 3 arguments, but got 2.(2554) */

fct(Enum.C); /* Error (expected)
~~~~~~~~~~~
Expected 3 arguments, but got 1.(2554) */

// …etc.

Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading