2

Following generic function takes a object of functions and returns an object with same propnames valued with returning value of the corresponding function.

const combineValues = obj => { 
      const res = {};
      for (const k in obj){
        res[k] = obj[k]()
      }
      return res;
    } 
const combined = combineValues({
  n: () => 1,
  s: () => 's'
}); // { n: 1, s: 's' }

Tryed to define a signature and an implementation for this function in Typescript

const combineValues =
  <K extends string> (obj: Record<K, () => any>): Record < K, any > => {
    const res = {} as Record<K, any>;
    for (const k in obj){
      res[k] = obj[k]()
    }
    return res;
  }

const combined = combineValues({
  n, s
}) // Record<"n" | "s", any>

but combined doesn't keep original typings for values

const combcombineValues = <K extends string, T>(obj: Record<K, () => T>): Record<K, T> => {
    const res = {} as Record<K, T>;
    for (const k in obj){
      res[k] = obj[k]()
    }
    return res;
  }

works only if all function props return same type T

is it possible to fully define it in Typescript ?

2 Answers 2

3

This can be done in the current version of TypeScript (v2.7) without waiting for conditional types, by using inference from mapped types, a feature of TypeScript in which functions can infer types in what seems like the "backwards" direction, from output to input.

First let's define the mapped type Funcs<T> which takes a plain object and turns it into an object whose properties are all functions returning the property types of the plain object:

type Funcs<T> = { [K in keyof T]: (...args:any[]) => T[K] }

Note how this type is basically backwards from what you want to do. Now let's type combineValues():

const combineValues = <T>(obj: Funcs<T>): T => {
    const res = {} as T;  // only change in body
    for (const k in obj) {
        res[k] = obj[k]()
    }
    return res;
}

which, as you can see, takes a Funcs<T> as input and returns a T as output. Let's see if it works:

const combined = combineValues({
    n: () => 1,
    s: () => 's'
}); // { n: number, s: string }

That's almost what you wanted. The only difference is that TypeScript tends to interpret () => 1 as a function that returns a number and not a function that returns the literal 1. There are things you can do to work around that; the simplest (although a bit repetitive) is to assert the return type literals:

const combined = combineValues({
    n: () => 1 as 1,
    s: () => 's' as 's'
}); // { n: 1, s: 's' }

Hope that helps. Good luck!

UPDATE: It was noted that the compiler appears to type combined as {n: any, s: any} in TypeScript 2.7. Fortunately, this only seems to be happening when you inspect the type with Intellisense, as (I think) noted in Microsoft/TypeScript#14041. If you actually use the value, you will see that it has the proper type:

combined.n = 0 // error, 0 is not assignable to 1
combined.s = 0 // error, 0 is not assignable to 's'

Playground link

So, I guess I stand by this answer, although the Intellisense bug is unfortunate. Good luck again!

Sign up to request clarification or add additional context in comments.

3 Comments

I'm trying your solution with TS v2.7.2 but compiler still yelds { n: any; s: any; } with no complaints. It works, however, with TS v2.8 (next)
Uh oh! I’ll look into it when I get to a real computer. If I can’t fix it I’ll remove the answer.
See update. It works in 2.7.2 but a bug in Intellisense is making it look like it doesn't. 🤷‍♂️
2

You can achieve this in typescript 2.8 using conditional types (2.8 in not yet released at the time of writing, you can get it via npm install -g typescript@next it is planned for a March release).

// ReturnType<T> below is from 2.8 and is a conditional type
type AllReturnTypes<T extends { [name: string]: (...args: any[]) => any }> = { [P in keyof T]: ReturnType<T[P]> }
const combineValues = <T extends { [name: string]: () => any }>(obj: T): AllReturnTypes<T> => {
    const res = {} as AllReturnTypes<T>;
    for (const k in obj) {
        res[k] = obj[k]()
    }
    return res;
}

const combined = combineValues({
    n: () => 1, s: () => ""
}) // will be of type { n: number, s: string }

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.