Home > Blockchain >  Make function accept generic input, return value with same keys, but with values from another type
Make function accept generic input, return value with same keys, but with values from another type

Time:02-01

I have the following types

type A = {
  foo: number
  bar: number
}

type B = {
  foo: string
  bar: string
}

And I want to write a function f() that takes a parameter that is completely or partially type A, and outputs an object with the same keys, but with types from type B.

For example,

f(a) = {foo: 1, bar: 2}
// should have return type {foo: string, bar: string}
f(a) = {foo: 1}
// should have return type {foo: string}
f(a) = {bar: 1}
// should have return type {bar: string}

I have tried to use generics, but since typescript doesn't check for extra fields, I get an error when I use keyof:

function f<T extends A>(input: T): {[K in keyof T]: B[K]} {
  //      Type 'K' cannot be used to index type 'B' ^^^^^

  // ... function implementation here
}

If this solution is to work, I'll have to somehow restrict T to only being a subset of type A, though I haven't found a way to do it that makes the compiler understand keyof T can index A.

The closest I have come is to using Partial, but again that is not exactly the behavior I want:

function f(input: Partial<A>): Partial<B> {
  // mock implementation, output does not matter, asking about types here
  const res: Partial<B> = {};
  let k: keyof typeof input;
  for (k in input) {
    const v = input[k];
    res[k] = parseInt(v ?? "0");
  }
  return res;
}

This is incorrect because the following inputs all give outputs of the same type:

const a = f({})
const b = f({foo: 1})
const c = f({bar: 2})
// a, b, c all have type {foo?: number, bar?: number)

// should be:
// a: {}
// b: {foo: string}
// c: {bar: string}

Is what I'm asking for possible in Typescript right now? Any solutions/workarounds would be much appreciated.

CodePudding user response:

My suggestion here is to make the function f() generic in the type of the keys K of the input parameter, and then you can represent the input as Pick<A, K> and the output type as Pick<B, K>, using the Pick<T, K> utility type:

declare function f<K extends keyof A>(input: Pick<A, K>): Pick<B, K>; 

Or you can inline the definition of Pick to get the following version:

declare function f<K extends keyof A>(input: { [P in K]: A[K] }): { [P in K]: B[K] };

Let's test it out:

const b0 = f({ foo: 1, bar: 2 });
/* const b0: {
    foo: string;
    bar: string;
} */

const b1 = f({ foo: 1 });
/* const b1: {
    foo: string;
} */

const b2 = f({ bar: 1 });
/* const b2: {
    bar: string;
} */

Looks good.

Playground link to code

  • Related