简体   繁体   中英

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

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.

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM