Rename keys of an object/interface typesafe

吃可爱长大的小学妹 提交于 2021-02-19 04:19:58

问题


I want to dynamically rename keys with a mapping function. For this, I've made this:

interface DateRange {
    startDate: string;
    endDate: string;
}

function format<K extends string>(range: DateRange, mapping: {[X in keyof DateRange]: K}): {[P in K]: string} {
    return {
        [mapping.startDate]: range.startDate,
        [mapping.endDate]: range.endDate,
    };
}

When I cast the return value of this function with as {[P in K]: string} it all works fine, but without the cast it doesn't compile.

Error Message:

TS2322: Type '{ [x: string]: string; }' is not assignable to type '{ [P in K]: string; }'.

From what I see both keys of the DateRange interface are used, so the keys of the return value should all be of type K.

How must the function look like, that a cast is not needed?

Example call:

const formatted = format(
    {startDate: 'startDateValue', endDate: 'endDateValue'}, 
    {startDate: 'start', endDate: 'end'}
);
// formatted = {end: 'endDateValue', start: 'startDateValue'}

回答1:


I can reproduce your problem, and it seems to have been noted before in a GitHub issue. That issue has been marked "fixed", but this part of it obviously has not. Anyway, this might just be a design limitation... the only way TypeScript could represent an object with a generic type parameter as a key is with a mapped type, and I doubt that anything in TypeScript will interpret an object literal as a mapped type automatically. I don't know if there's a more appropriate existing issue in GitHub or if someone should open one.

For now, you have to deal with workarounds. The one you used is fine (assert the type of the return value), if you're careful. A slightly more type-safe one is to add properties one by one, as in:

function format<K extends string>(
  range: DateRange, 
  mapping: { [X in keyof DateRange]: K }
): { [P in K]: string } {
  const ret = {} as { [P in K]: string }; // assertion
  ret[mapping.startDate] = range.startDate; // safer
  ret[mapping.endDate] = range.endDate; // safer
  return ret;
}

It still won't complain if you leave out a property, but it will stop you from adding random other string-valued computed properties:

  const badProp: string = "uh oh"
  return {
    [mapping.startDate]: range.startDate,
    [mapping.endDate]: range.endDate,
    [badProp]: "whoops" // no error
  } as { [P in K]: string };

vs

  const badProp: string = "uh oh"
  const ret = {} as { [P in K]: string };
  ret[mapping.startDate] = range.startDate;
  ret[mapping.endDate] = range.endDate;
  ret[badProp] = "whoops"; // error (implicit any)

That might not be worth it to you, though, and your type assertion is reasonable. Hope that's helpful. Good luck!



来源:https://stackoverflow.com/questions/50871996/rename-keys-of-an-object-interface-typesafe

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!