问题
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