Everyday TypeScript: Pick and Omit
Welcome to the Pick and Omit lesson!
This lesson is shown as static text below. However, it's designed to be used interactively. Click the button below to start!
Sometimes we want a type with only some of the properties from another type. The TypeScript language designers could have added a special language feature to do that, but they didn't. Instead, they gave us two utility types. These are regular TypeScript types, defined using regular TypeScript code. That code just happens to come with the TypeScript compiler.
The
Picktype lets us "pick" one or more properties from an object type, creating a new object type with only those properties.>
type User = {name: stringemail: stringage: number};type NameOnly = Pick<User, 'name'>;const amir: NameOnly = {name: 'Amir'};amir;Result:
{name: 'Amir'}>
type User = {name: stringemail: stringage: number};type NameOnly = Pick<User, 'name'>;const amir: NameOnly = {name: 'Amir', email: 'amir@example.com'};amir;Result:
type error: Object literal may only specify known properties, and 'email' does not exist in type 'NameOnly'.
What if we want to pick multiple properties? It would be reasonable to guess that we'd do it with
Pick<User, 'name', 'email'>. However, that's not possible in TypeScript: generic types must take a fixed number of type parameters. Instead, we pick multiple object properties via a union of the properties' names.>
type User = {name: stringemail: stringage: number};type NameAndEmail = Pick<User, 'name' | 'email'>;const amir: NameAndEmail = {name: 'Amir'};amir;Result:
type error: Property 'email' is missing in type '{ name: string; }' but required in type 'NameAndEmail'.>
type User = {name: stringemail: stringage: number};type NameAndEmail = Pick<User, 'name' | 'email'>;const amir: NameAndEmail = {name: 'Amir', email: 'amir@example.com'};amir;Result:
{name: 'Amir', email: 'amir@example.com'}Here's an example of how we might use this. Suppose that we have a web application with a JSON API. The API is for a forum system: users can register accounts, create threads, and post comments in the threads. We define large, complex types for the concepts in the API, like
User,Thread,Comment, etc.Individual API endpoints will only need to include small pieces of those objects. The account page will need most of the user's properties, but other pages will only need a few user properties. When showing an entire thread, we'll need a lot of details about the thread and its comments. But when showing a list of many threads, we'll only need the thread's subject and the number of comments in it.
We can use
Pickto define these API endpoint types. When showing the thread list, we can pick out only the properties that we need:Pick<Thread, 'subject' | 'commentCount'>. By usingPick, we avoid repeatedly defining separate thread types that duplicate the same properties over and over again.The
Omittype is the opposite ofPick. WhereasPickselects some properties to include,Omitincludes all properties except the ones we specify.>
type User = {name: stringemail: stringage: number};type NameOnly = Omit<User, 'email' | 'age'>;const amir1: {name: string} = {name: 'Amir'};const amir2: NameOnly = amir1;amir2;Result:
{name: 'Amir'}>
type User = {name: stringemail: stringage: number};// These two types are equivalent!type NameOnly1 = Pick<User, 'name'>;type NameOnly2 = Omit<User, 'email' | 'age'>;const amir1: {name: string} = {name: 'Amir'};const amir2: NameOnly1 = amir1;const amir3: NameOnly2 = amir1;amir3;Result:
{name: 'Amir'}>
type User = {name: stringemail: stringage: number};type NameOnly = Pick<User, 'name'>;type NameAndEmail = Omit<User, 'age'>;const amir1: {name: string} = {name: 'Amir'};const amir2: NameOnly = amir1;const amir3: NameAndEmail = amir1;amir3;Result:
type error: Property 'email' is missing in type '{ name: string; }' but required in type 'NameAndEmail'.Here's a code problem:
The code below defines an
Albumtype. We want to build a new type that contains an album's numerical statistics, but not its name. Fill in thePicktype to select only thecopiesSoldandreleaseYearproperties.type Album = {name: stringcopiesSold: numberreleaseYear: number};type AlbumStats = Pick<Album, 'copiesSold' | 'releaseYear'>;const stats: AlbumStats = {copiesSold: 24400000,releaseYear: 1973,};stats;- Goal:
{copiesSold: 24400000, releaseYear: 1973}- Yours:
{copiesSold: 24400000, releaseYear: 1973}