Reputation: 2868
I have an interface User
:
interface User {
_id : string;
name : string;
email : string;
password: string;
phone : number;
}
I have another interface UpdatedBy
:
interface UpdatedUser {
id : string;
name: string;
}
I know I can use Pick
, but I want to rename _id
to id
in the UpdatedUser
interface.
type UpdatedUser = Pick<User, '_id' | 'name'>; // How can I turn _id into id?
Update: I basically want to do a cleaner version of this:
export interface UpdatedUser extends Pick<User, 'name'> {
id : Extract<User, '_id'>;
}
Upvotes: 29
Views: 19946
Reputation: 31949
I have another, much simpler, solution which is easier to use with a syntax inspired by destructuring. It also works well with intellisense when picking the keys from the original object.
const {a: x, b: y} = {a: 1, b: 'b'}
// x = 1
// y = 'b'
type A = { a: string, b: number, c: boolean, d: any }
type B = PickAs<A, 'a:x' | 'b:y' | 'c'>
// B = { x: string; y: number; c: boolean; }
type PickAs<T, K extends Exclude<keyof T, K extends `${infer A}:${string}` ? A : never>> =
Normalize<
& UnionToIntersection<K extends `${infer A}:${infer B}` ? { [key in B]: T[A] } : never>
& Pick<T, Extract<K, keyof T>>
>
type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends
(k: infer I) => void ? I : never
type Normalize<T> = { [K in keyof T]: T[K] }
The stripped type looks like this:
type PickAs<T, K extends keyof T> = // K = e.g. `'a:x' | 'b:y' | 'c'`
K extends `${infer A}:${infer B}` // 1
? { [P in B]: T[A] } // 2
: never // 3 - ignore everything else
// Compare to classic Pick
type Pick<T, K extends keyof T> =
{ [P in K]: T[P] }
K
extends a string *:*
, extract it's constituents into A
and B
{ [B]: T[A] }
'a:c' | 'b:d'
, but not for 'a' | 'b'
.To support original keys of T
, we add
Pick<T, Extract<K, keyof T>> // From `K` only extract original keys of `T` and Pick.
The resulting type may look like this
type A = { a: string, b: number, c: boolean, d: any }
type B = PickAs<A, 'a:x' | 'b:y' | 'c'>
// result
type B = ({
x: string;
} | {
y: number;
}) & Pick<A, "c">
Not perfect.
UnionToIntersection
resolves {x} | {y}
into {x} & {y}
.Normalize
merges {x} & {y} & Pick<A,'c'>
into one object. { x, y, c }
And the last thing to do is to add intellisense support.
// Before:
type PickAs<T, K extends keyof T>;
// After:
type PickAs<T, K extends Exclude<keyof T, K extends `${infer A}:${string}` ? A : never> | `${string}:${string}`>;
K
matches ?:?
, take the first constituent {A}:?
and return it (? A : never
) instead of the whole string ?:?
. Exclude<keyof T, A>
then removes it from the allowed pool of values, which makes it disappear from intellisense.| `${string}:${string}`
then adds the string back, so that the extends clause doesn't give error that abc:xyz
"is not assignable to type keyof T"Upvotes: 0
Reputation: 308
I really liked ford04's Dynamic version for multiple properties, but I wanted optional keys to remain optional, like in Alireza Mirian's answer.
So I've combined the two here.
type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (
k: infer I,
) => void
? I
: never;
export type RenameProperties<
T,
R extends {
[K in keyof R]: K extends keyof T ? PropertyKey : "Error: key not in T";
},
> = Omit<T, keyof R> &
UnionToIntersection<
{
[P in keyof R & keyof T]: undefined extends T[P]
? { [PP in R[P]]?: T[P] }
: { [PP in R[P]]: T[P] };
}[keyof R & keyof T]
>;
Upvotes: 2
Reputation: 6672
The problem with ford04's answer is that it doesn't keep optional/required property of renamed keys. Here is how you can rename a prop while accounting for the key being optional or required:
type KeyRenamed<T, K extends keyof T, R extends PropertyKey> = Omit<
T,
K
> &
(undefined extends T[K] ? { [P in R]?: T[K] } : { [P in R]: T[K] });
type NameOptional = {
name?: string;
}
type NameRequired = {
name: string;
}
type DisplayNameRequired = KeyRenamed<NameRequired, 'name', 'displayName'>
type DisplayNameOptional = KeyRenamed<NameOptional, 'name', 'displayName'>
const displayNameOptional: DisplayNameOptional = { displayName: 'Ali' }
const displayNameOptional_missing: DisplayNameOptional = { } // no error, since displayName is kept optional after rename
const displayNameRequired: DisplayNameRequired = { displayName: 'Ali' }
const displayNameRequired_missing: DisplayNameRequired = { } // error, since displayName is kept required after rename
You can apply the same thing to those more advanced types in that answer, if for example you want to rename multiple keys at once.
Upvotes: 1
Reputation: 74510
There is no built-in type for a renaming Pick
, fortunately we can create one with reasonable effort.
type IdRenamed = Omit<User, "_id"> & { id: User["_id"] }
// { name: string; email: string; password: string; phone: number; id: string;}
type PickRename<T, K extends keyof T, R extends PropertyKey> =
Omit<T, K> & { [P in R]: T[K] }
type T21 = PickRename<User, "_id", "id"> // same type as above
type T22 = PickRename<User, "foo", "id"> // error, foo is no property
TS 4.1 Alternative: use mapped type as
clauses. Its advantage is that readonly
or optional (?
) modifiers of properties are preserved (see homomorphic mapped types 1, 2 for more details).
type PickRename<T, K extends keyof T, R extends PropertyKey> = {
[P in keyof T as P extends K ? R : P]: T[P]
} // type instantiation same as previous example
type PickRenameMulti<T, R extends
{ [K in keyof R]: K extends keyof T ? PropertyKey : "Error: key not in T" }
> = Omit<T, keyof R> & UnionToIntersection<
{ [P in keyof R & keyof T]: { [PP in R[P]]: T[P] } }[keyof R & keyof T]
>
type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends
((k: infer I) => void) ? I : never
type T31 = PickRenameMulti<User, { _id: "id"; name: "firstName" }>
type T32 = PickRenameMulti<User, { foo: "id" }> // error, foo is no property
Note: See the great UnionToIntersection
type for more details on the helper.
type PickRenameMulti<T, R extends
{ [K in keyof R]: K extends keyof T ? PropertyKey : "Error: key not in T" }
> = { [P in keyof T as P extends keyof R ? R[P] : P]: T[P] }
_
prefix from all property keystype DropUnderscore<T> = {
[K in keyof T as K extends `_${infer I }` ? I : K]: T[K]
};
type T4 = DropUnderscore<User> // "_id" and "_email" renamed to "id", "email"
Upvotes: 56
Reputation: 1419
A slightly cleaner version would be ...
export interface UpdatedUser extends Pick<User, 'name'> {
id: User['_id'];
}
... but I'm not sure how to rename it on-the-fly as you're suggesting. It's an interesting use-case.
Upvotes: 8