7
votes

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'>;
}
3
Could you explain what happen when you try to rename it ?Nicolas
I didn't rename it, yet. I just want the id in UpdatedUser to refer to the _id in User.yaserso
@yaharga gave a fix for PickRenameMulti and some improvement with PickRenameMultiV2 in my updated answer, in case you are interested.ford04
@ford04 Thanks for the update, appreciate it!yaserso

3 Answers

19
votes

There is no built-in type for a renaming Pick, fortunately we can create one with reasonable effort.

Simple variant

type IdRenamed = Omit<User, "_id"> & { id: User["_id"] }
// { name: string; email: string; password: string; phone: number; id: string;}

Playground

Dynamic version for single property

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

Playground

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

Playground

Dynamic version for multiple properties

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.

Playground

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] }

Playground

TS 4.1: Drop _ prefix from all property keys

type 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"

Playground

4
votes

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.

0
votes

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.

playground