Home > OS >  Computed return type based on parameters
Computed return type based on parameters

Time:10-26

Say I have a function like this:

  static async getPets({ petType, inverseOrder }: { petType: string; inverseOrder?: boolean }) {
    const [petsFound, totalFound] = await getPetsByType(petType, inverseOrder);
    return {
      [petType]: petsFound, // array topped to 20 results
      totalFound // count all found without limit of 20
    };
  }

I want the return type of this function to be computed based on the petType parameter, which can be any string, so that:

  • getPets({ petType: 'dogs' }) has return type { dogs: any[], totalFound: number }

  • getPets({ petType: 'cats' }) has return type { cats: any[], totalFound: number }

  • etc.

The current implementation has the return type { [x: string]: any; totalFound: any; } which is not very useful since it doesn't check that the key returned is the same as the one used as param.

How do I do this?

CodePudding user response:

Not sure if this is optimized, but it works:

TS Playground link

declare function getPetsByType (petType: string, inverseOrder?: boolean): Promise<[any[], number]>;

type Awaited<T> = T extends Promise<infer U> ? U : T;

type GetPetsResult<T extends string> = {
  [k in T]: Awaited<ReturnType<typeof getPetsByType>>[0];
} & {
  totalFound: Awaited<ReturnType<typeof getPetsByType>>[1];
};

async function getPets <T extends string>({ petType, inverseOrder }: {
  petType: T;
  inverseOrder?: boolean;
}): Promise<GetPetsResult<T>> {
  const [petsFound, totalFound] = await getPetsByType(petType, inverseOrder);
  return {
    [petType]: petsFound,
    totalFound,
  } as GetPetsResult<T>;
}

async function main () {
  const result = await getPets({petType: 'giraffe', inverseOrder: false});
  console.log(result.giraffe.length === result.totalFound); // ok
  console.log(result.cat.length === result.totalFound); // Error: Property 'cat' does not exist on type 'GetPetsResult<"giraffe">'.(2339)
}

CodePudding user response:

Not getting into implementation details of Promise and focusing only on the return type based on the parameter passed, you could do the following:


// transform generic into literal type
type StringLiteral<T> = T extends string ? string extends T ? never : T : never;

// define function
function getPets<Key>({ petType }: { petType: StringLiteral<Key>}): Record<StringLiteral<Key>, number[]> & {totalFound: number;} {
    return {
        [petType] : [1, 2, 3],
        totalFound: 123
    } as  Record<StringLiteral<Key>, number[]> & {totalFound: number}
}

// pass in 'petType' and get a a return type based on whatever you defined on that param
const {} = getPets({petType: 'cats'}) // return type : Record<"cats", number[]>

  • Related