Home > Blockchain >  Get function result or value from an object by property
Get function result or value from an object by property

Time:04-27

TS Playground

I'm trying to figure out the correct type for a utility function getProp that will index an object by key and return the primitive value at that index or the result of the function at that index.

declare interface App {
  name: () => string
  version: string
  id: number
}

const getProp = function<C extends App, S extends keyof App>(context: C, prop: S) : C[S] extends (...args: any[]) => any ? ReturnType<C[S]> : C[S] {
  const method = context[prop];
  if (typeof method === 'function') {
    return method(); // Error: Type 'string' is not assignable to type 'C[S] extends (...args: any[]) => any ? ReturnType<C[S]> : C[S]'.
  } else {
    return method; // Error: Type '() => string' is not assignable to type 'C[S] extends (...args: any[]) => any ? ReturnType<C[S]> : C[S]'.
  }
};

declare var app : App;

// These are all correct despite the errors
getProp(app, "id") // => number
getProp(app, "name") // => string
getProp(app, "version") // => string

The type presented here "works" but TypeScript is giving me errors on the lines indicated. Is there a better way to type this properly without errors or should I just drop in my old friend //@ts-ignore?

CodePudding user response:

C[S] type is getting from App. You don't need to use extends but itself directly in the returned params

We also cannot predict the function's returned type, so I'd use

ReturnType<(...args: any[]) => any>

The full possible change is below

declare interface App {
  name: () => string
  version: string
  id: number
}

const getProp = function<C extends App, S extends keyof App>(context: C, prop: S) : C[S] | ReturnType<(...args: any[]) => any> {
  const method = context[prop];
  if (typeof method === 'function') {
    return method(); //TS knows method `name` returns a string but not C[S]
  } else {
    return method; //TS knows this as C[S]
  }
};

declare var app : App;

// These are all correct despite the errors
getProp(app, "id") // => number
getProp(app, "name") // => string
getProp(app, "version") // => string

You can verify it with this playground

If you can predict the returned types from name, you can follow the below types passing

C[S] | ReturnType<() => string>

Or

C[S] | string

CodePudding user response:

When we use Generic Functions the actual type instantiation happens when we invoke it, getProp<App, 'name'>

Hence the Typescript compiler needs to be sure that the types we are using must be compatible and follow the constraints.

Since the Return Type of getProp is conditional we can make use of assertion/aliasing to inform the typescript compiler of the state in which this branch will be reached

In your code, the compiler was complaining that it is possible that you are returning some code (which might not match the return value) at the time of declaration!

Hence by aliasing it, we make sure that the types will be of this ReturnType only!

To avoid Repetition I created a new type alias PropReturnType<T>

declare interface App {
  name: () => string
  version: string
  id: number
}

type PropReturnType<T> = { [K in keyof T]: T[K] extends (...args: any[]) => any ? ReturnType<T[K]> : T[K] }


const getProp = function<C extends App, S extends keyof C>(context: C, prop: S): PropReturnType<C>[S]{
  const method = context[prop];
  if (typeof method === 'function') {
    return method();
  } else {
    return method as PropReturnType<C>[S];
  }
};



declare var app: App;

getProp(app, "id") // => number
getProp(app, "name") // => string
getProp(app, "version") // => string

CODE PLAYGROUND

  • Related