Question

Type return of a function depending of a parameters as a part of the returning object

I'm facing a issue I don't know if it's possible to resolve.

function getOptions(
  period: { first: string; last: string },
  prefix?: string
){
  if (prefix) {
    return {
      [`${prefix}_first`]: formatDay(period.first),
      [`${prefix}_last`]: formatDay(period.last),
      [`${prefix}_month`]: format(new Date(), 'MMMM', {
        locale: i18n.t('datefns:format', { returnObjects: true }),
      }),
    }
  }

  return {
    first: formatDay(period.first),
    last: formatDay(period.last),
    month: format(new Date(), 'MMMM', {
      locale: i18n.t('datefns:format', { returnObjects: true }),
    }),
  }
}

I would like typescript return the right typed object in function of the prefix parameter. How can I do it ?

The expected result is

getOptions({first: '01', last: '10'}) // {first: string, last: string, month: string}
getOptions({first: '01', last: '10'}, 'christmas') // {christmas_first: string, christmas_last: string, christmas_month: string}
 2  45  2
1 Jan 1970

Solution

 2

Refactor your method to accept two generic parameters for both the period (T) and the prefix (P). That allows you to infer their specific types. Then you can map the keys of the period to start with the prefix unless it is undefined. (meaning no prefix argument was passed to the function). I added the additional return type property month with a simple intersection type. It might be a good idea to create type aliases for these literals.

declare function getOptions<
  T extends { first: string; last: string },
  P extends string | undefined = undefined,
>(
  period: T,
  prefix?: P,
): {
  [K in keyof (T & { month: string }) as `${P extends undefined
    ? ""
    : `${P}_`}${K extends string ? K : never}`]: (T & { month: string })[K];
};

const options1 = getOptions({ first: "01", last: "10" });
// const options1: {
//     first: string;
//     last: string;
//     month: string;
// }

const options2 = getOptions({ first: "01", last: "10" }, "christmas");
// const options2: {
//     christmas_first: string;
//     christmas_last: string;
//     christmas_month: string;
// }

TypeScript Playground

2024-07-24
Behemoth

Solution

 0

You would want to add an interface like so:

interface MyDynamicObject {
  [key: string]: any;
}

And then your function can work like this:

  getOptions(
    period: { first: string; last: string },
    prefix: string = ''
  ) {
    const res: MyDynamicObject={};
    prefix = prefix.length ? prefix + '_' : prefix;
    res[prefix + 'first'] = formatDay(period.first),
    res[prefix + 'last'] = formatDay(period.last),
    res[prefix + 'month'] = format(new Date(), 'MMMM', {
      locale: i18n.t('datefns:format', { returnObjects: true }),
    })
    return res;
  }
2024-07-24
Taha Zgued