Valerii Strilets
Valerii Strilets

Reputation: 318

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!

Upvotes: 0

Views: 553

Answers (1)

jcalz
jcalz

Reputation: 329523

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

Upvotes: 2

Related Questions