首页 > 解决方案 > How to type a function that transforms some properties of an input object

问题描述

Does anyone know how one would typ a transform function where only part of the keys are known/relevant and one of the known keys are optional? E.g.

const transform = <T extends BaseAkpiDto>(akpiDTO: T) => {
  const { startDate, periodData, ...akpiBase } = akpiDTO;
  const withMoments = {
    ...akpiBase,
    startDate: moment.utc(startDate),
  };

  if (!periodData) {
    return withMoments;
  }

  return {
    ...withMoments,
    lineData: akpiDtoToLineData(withMoments.startDate, periodData),
  };
};

interface BaseAkpiDto {
  startDate: string;
  endDate: string;
  periodData?: PeriodDto[];
}

const test: WithPeriodData = akpiDTOtoAkpiData({
  id: 1,
  name: 'my name',
  startDate: '2019',
  periodData: [] as PeriodDto[],
});

interface WithPeriodData {
  id: number;
  name: string;
  startDate: Moment;
  lineData: Period[];
}

I'm unable to get the return type to include a lineData property if (and only if) there is a lineData property on the input :(

Typescript complains to my testvariable with the following message:

Property 'lineData' is missing in type 'Pick<{ id: number; name: string; startDate: string; periodData: PeriodDto[]; }, "id" | "name"> & { startDate: Moment; endDate: Moment; }' but required in type 'WithPeriodData'.

标签: typescripttypescript-typingstypescript-generics

解决方案


Function transform has conditional behavior. Looking on the control flow it can return or object which contains lineData or not. Therefor you cannot assign the return which is a union IHaveLineData | IDoNotHaveLineData to IHaveLineData as the return is just wider type. Imagine you want to assign smth which is string | number to number, because of the same reason you can't.

To be sure you get the wanted structure you need to append additional control flow to ensure the type. So just:

// at this level test is infered as with or without lineData
const test= akpiDTOtoAkpiData({
  id: 1,
  name: 'my name',
  startDate: '2019',
  periodData: [] as PeriodDto[],
});

if ('lineData' in test) {
  // here test is precisely the type with lineData
}


推荐阅读