TypeScript Pick nested object

eye-catch JavaScript/TypeScript

There might be some cases where we don’t want to create a new type/interface that is actually used in a different interface. Assume that we have the following interface.

export interface Person {
    name: string;
    age: number;
    lang: string;
    address: {
        postalCode: number;
        city: string;
        street: string;
    }
}

How can we pick some of these definitions?

Sponsored links

Utility Type Pick

TypeScript offers Utility type called “Pick”. This type picks specified properties’ types from a type/interface.

type PickPerson = Pick<Person, "name" | "age" | "address">;
const person: PickPerson = {
    name: "Yuto",
    age: 34,
    // Type '{ name: string; age: number; lang: string; }' is not assignable to type 'PickPerson'.
    // Object literal may only specify known properties, and 'lang' does not exist in type 'PickPerson'.
    // lang: "ja",
    address: {
        city: "hoge-city",
        postalCode: 12345,
        street: "super-street",
    },
};

There is an error on “lang” property because it is not specified in Pick type even though it exists in Person interface. This is useful when we want to pick top-level properties but it can’t be used for second-level properties.

“address” property is an object. When we want to get the definition of address property, we might want to try the following code first.

type AddressType = Pick<Person, "address">;
const address: AddressType = {
    address: {
        city: "hoge-city",
        postalCode: 12345,
        street: "super-street",
    },
};

But this is not what we want. We want only the 3 properties without “address” property. Dot chain is not available in the string.

// Type '"address.postalCode"' does not satisfy the constraint 'keyof Person'.ts(2344)
type PostalCodeType = Pick<Person, "address.postalCode">;
Sponsored links

Access the property name with brackets

A nested property in interface/type can be accessed with brackets.

type AddressType = Person["address"];
const address: AddressType = {
    city: "hoge-city",
    postalCode: 12345,
    street: "super-street",
};

It gets the definition of address property. If we want to get one of the types in the address property, just add additional brackets.

type PostalCodeType = Person["address"]["postalCode"];
const postalCode: PostalCodeType = 123;

// 'errorPostalCode' is declared but its value is never read.ts(6133)
// Type 'string' is not assignable to type 'number'.ts(2322)
const errorPostalCode: PostalCodeType = "123";

The PostalCodeType is number. Therefore, string can’t be assigned.

Get data type of an Array

Let’s try the same thing to array property.

export interface Department1 {
    name: string;
    members: {
        name: string;
        age: number;
    }[];
}

type PickNestedPersons = Pick<Department1, "members">;
const person: PickNestedPersons = {
    members: [{
        name: "Yuto",
        age: 34,
    }],
};

// Type '"members.name"' does not satisfy the constraint 'keyof Department1'.ts(2344)
// type PickNestedPerson = Pick<Department1, "members.name">;

If using Pick<>, we have to add members property. The basic solution would be to extract the definition into a different type/interface.

export interface Department2 {
    name: string;
    members: Person[];
}

const person: Person = {
    name: "Yuto",
    age: 34,
    lang: "ja",
    address: {
        city: "hoge-city",
        postalCode: 12345,
        street: "super-street",
    }
};

The Person interface has already been defined and its definition has extra properties that are not necessary. If we want only name/age in Department1, access it with brackets and pass 0 to the index.

type PickNestedPerson = Department1["members"][0];
const person: PickNestedPerson = {
    name: "Yuto",
    age: 34,
};

We can define Company interface like this. If the Company interface requires all properties in Department interface, we should write in the second way, otherwise, first way.

export interface Company1 {
    name: string;
    departments: {
        name: Department1["name"];
        members: Department1["members"];
    }[];
}

export interface Company2 {
    name: string;
    departments: Department2[];
}

Comments

Copied title and URL