Search code examples
typescripttypespropertiesreturnarguments

Typescript. How to get type of property in object from arguments as return type


I hope you can help me with TypeScipt ;)

Question:

How can I get a type from the object property that I receive from arguments?

Code:

const someVariable = 'test=123&test2=456';

// I want by default return type { [key: string]: any }[],
// but if options.parse === false { [key: string]: string }[]
function someFunction(options: string | IOptions) {
  if (typeof options === 'string') {
    return someVariable.split('&').map((part) => {
      const [key, value] = part.split('=');
          
      return { [key]: JSON.parse(value) };
    });
  }

  return someVariable.split('&').map((part) => {
      const [key, value] = part.split('=');
          
      if (options.parse) {
        return { [key]: JSON.parse(value) };
      }
          
      return { [key]: value };
    });
}

someFunction()
// => { [key: string]: any }[]

someFunction({ parse: false })
// => { [key: string]: string }[]

Thank you for your help!


Solution

  • One way to do this is to make someFunction() an overloaded function with multiple call signatures corresponding to the different input-output relationships you want to see:

    // call signatures
    function someFunction(options: IOptions & { parse: false }): Array<{ [k: string]: string }>;
    function someFunction(options: string | IOptions): Array<{ [k: string]: any }>;
    
    // implementation
    function someFunction(options: string | IOptions): Array<{ [k: string]: any }> {
      // impl unchanged
    }
    

    And you can see when you call it that it works as expected:

    const valAny = Object.values(someFunction("aString")[0])[0]
    // const valAny: any
    
    const valString = Object.values(someFunction({ parse: false })[0])[0]
    // const valString: string
    

    You can also achieve this result with a generic function that returns a conditional type:

    function someFunction<T extends string | IOptions>(
        _options: T
    ): Array<{ [k: string]: T extends { parse: false } ? string : any }> {
       const options: string | IOptions = _options;
       // impl unchanged after this
    }
    

    And it behaves the same way for the calls we tested above:

    const valAny = Object.values(someFunction("aString")[0])[0]
    // const valAny: any
    
    const valString = Object.values(someFunction({ parse: false })[0])[0]
    // const valString: string
    

    Each solution has its benefits and drawbacks, depending on the use case. Neither solution guarantees type safety inside the implementation of the function, so you need to be careful that the implementation really does conform to the call signatures you are using.

    Playground link to code