Reputation: 9165
I have this:
type Two = {
one: number,
two: string,
three: boolean
}
I want it to create a type that would look like this:
type RenamedTwo = {
one: number,
two: string,
four: boolean // difference
}
Tried to create it this way:
type Rename<T, K extends keyof T, N> = Pick<T, Exclude<keyof T, K>> & { [N]: T[K] }
In an attempt to use this way:
type Renamed = Rename<Two, 'three', 'four'>
But TSlint marks [N]
as error and gives this error message:
[ts] A computed property name in a type literal must refer to an expression whose type is a literal type or a 'unique symbol' type. [ts] 'N' only refers to a type, but is being used as a value here.
Upvotes: 19
Views: 23886
Reputation: 436
I had a similar need but I wanted to be able to rename as many properties of an object that I wanted to so I came up with this function where I pass the object to rename and the associated mapping:
type Mapped<
Type extends object,
Mapping extends Partial<Record<keyof Type, string>>,
> = {
[Property in keyof Type as Property extends keyof Mapping
? Mapping[Property] extends string
? Mapping[Property]
: Property
: Property]: Type[Property];
};
export function renameProperties<
Type extends object,
Mapping extends Partial<Record<keyof Type, string>>,
Result extends Mapped<Type, Mapping>,
>(obj: Type, mapping: Mapping): Result {
return Object.entries(obj).reduce<Result>((acc, [key, value]) => {
const newKey = mapping[key] ?? key;
return {
...acc,
[newKey]: value,
};
}, {} as Result);
}
I used the remapping ability shared by @banana :)
Upvotes: 0
Reputation: 151
In the current typescript version 4.6.2, there is a remapping gramma to use. It can be implement this much easier.
type RenameByT<T, U> = {
[K in keyof U as K extends keyof T
? T[K] extends string
? T[K]
: never
: K]: K extends keyof U ? U[K] : never;
};
type Two = { one: number; two: string; three: boolean };
// returnType = { one: number; two: string, four: boolean };
type renameOne = RenameByT<{three: 'four', five: 'nouse'}, Two>;
// returnType = { x: number, y: string, z: boolean; }
type renameAll = RenameByT<{one: 'x', two: 'y', three: 'z'}, Two>;
The RenameByT
type can separate by several parts.
The explain is for the example renameOne
K in keyof U
means all the key in U
. For the example is one | two | three
as
clause since ts4.1 can use. but i can't use in 4.4.4 but can use in 4.6.2. This is use to rename Key K
by the condiction typeK extends keyof T
. keyof T means three | five
.T[K] extends string
means T[K]
is string. T['three']
is string so returns four
, T['five']
returns nouse
;K extends keyof U
, so T['three']
is satisfy so return { four: U['three'] }
means { four: boolean}
reference:
https://github.com/microsoft/TypeScript/issues/40833 https://www.typescriptlang.org/docs/handbook/2/mapped-types.html
Upvotes: 15
Reputation: 1672
It is perhaps worth mentioning that you can solve the OP's original problem without writing a generic type like Rename
. If you just want to do it once, you could do something much simpler.
type RenamedTwo = { [Property in keyof Two as Property extends 'three' ? 'four': Property]: Two[Property] }
Upvotes: 3
Reputation: 249656
You need to use a mapped type for the renamed property as well:
type Two = {
one: number,
two: string,
three: boolean
}
type Rename<T, K extends keyof T, N extends string> = Pick<T, Exclude<keyof T, K>> & { [P in N]: T[K] }
type Renamed = Rename<Two, 'three', 'four'>
Note that this will not work as expected if you provide more properties:
type Renamed = Rename<Two, 'two' |'three' , 'four' | 'five'> // will be Pick<Two, "one"> & {
// four: string | boolean;
// five: string | boolean;
// }
Upvotes: 22