Home > Blockchain >  Infer params and return types based on generic interface
Infer params and return types based on generic interface

Time:09-08

I am building a function which accepts an interface and I want this to infer the return types from the 'key' in my options object passed as a function param

My desired results would be something like this reduced code example:

const stringKeyValue = createKeyValueMapped<{a: string, b: number}>({
 key: "a", // key: "a"
 value: "hi",  // value: string
 transform: (value) => value   ' more text', // (value: string) => string
}); 

stringKeyValue.key;  // key: 'a'
stringKeyValue.value; // value: string 


But with my current code the key type is 'a' | 'b' and the value type is 'string' | 'number'.

My current broken solution is:

export type KeyValue<
  Inputs,
  Key extends keyof Inputs,
  Value = Inputs[Key]
> = {
  key: Key;
  value: Value;
  transform: (value: Value) => Value;
};

type KeyValuesMapped<Inputs> = {
  [Key in keyof Inputs]: KeyValue<Inputs, Key>;
}[keyof Inputs];

const createKeyValueMapped = <Inputs,>({ key, value }: KeyValuesMapped<Inputs>) =>
  ({
    key,
    value,
  });

I have tried to create a type for KeyValue which includes the options inside a function and type the returns but the completely breaks the mapping.

Fuller example is in this Link to Playground

CodePudding user response:

Currently, the key property is not generic. The function does not know or care which value you pass to it. The output type can therefore not change based on the key.


The "easy" solution would be to add a generic type for key.

const createKeyValueMapped = <
  Inputs, 
  K extends keyof Inputs
>({ key, value }: KeyValue<Inputs, K>) =>
  ({
    key,
    value,
  });

The caller would have to specify the key explicitly when calling the function since TypeScript does not support partial type inference yet.

const stringKeyValue = createKeyValueMapped<CustomInputs, "a">({
  key: "a",
  value: "hi",
  transform: (value) => value   ' more text',
});

If we want to avoid explicitly setting the second generic type, we can use currying.

const createKeyValueMapped = <Inputs,>() =>
  <K extends keyof Inputs>({key, value}: KeyValue<Inputs, K>) => ({
    key,
    value,
  });

const stringKeyValue = createKeyValueMapped<CustomInputs>()({
  key: "a",
  value: "hi",
  transform: (value) => value   ' more text',
});

The outer function takes an explicit generic type while the inner function infers the key.

stringKeyValue.key; // "a"
stringKeyValue.value; // string

Playground

  • Related