Execute Program

Advanced TypeScript: Mapped Types With Index Signatures

Welcome to the Mapped Types With Index Signatures lesson!

This lesson is shown as static text below. However, it's designed to be used interactively. Click the button below to start!

  • We've seen mapped types on objects with explicit properties, like {email: string}. We can also apply mapped types to index signature types. Remember that index signatures are types like {[userName: string]: number}. That type allows any string as a property name, but all properties must have numbers as their values.

  • In the example below, we have a LoginCounts type. It uses an index signature to count how many times each user has logged in. We only know the users' names at runtime, not at compile time, so we have to allow the type's property names to be any string.

  • >
    type LoginCounts = {[userName: string]: number};

    const loginCounts: LoginCounts = {
    Amir: 5,
    Betty: 11,
    };

    loginCounts.Betty;
    Result:
    11Pass Icon
  • With that type, a user who has never logged in would presumably have a login count of 0. However, suppose that some other part of the system expects this to be null rather than 0. We can build a new mapped type that works like LoginCounts, but also allows null.

  • >
    type Nullable<T> = {
    [K in keyof T]: T[K] | null
    };

    type LoginCounts = {[userName: string]: number};

    const loginCounts: Nullable<LoginCounts> = {
    Amir: 5,
    Betty: null,
    };
    loginCounts.Betty;
    Result:
    nullPass Icon
  • Many of TypeScript's built-in utility types are implemented as mapped types. Partial is one example. We can use those utility types on types with index signatures, like we did above with our own mapped type. Normally, our LoginCounts type doesn't allow us to use undefined as a property value. But if we wrap it in Partial then it will allow undefined. (Remember that Partial makes every property optional by unioning its type with undefined.)

  • >
    type LoginCounts = {[userName: string]: number};

    const loginCounts: LoginCounts = {
    Amir: 5,
    Betty: undefined,
    };
    loginCounts.Betty;
    Result:
    type error: Type 'undefined' is not assignable to type 'number'.Pass Icon
  • >
    type LoginCounts = {[userName: string]: number};

    const loginCounts: Partial<LoginCounts> = {
    Amir: 5,
    Betty: undefined,
    };
    loginCounts.Betty;
    Result:
    undefinedPass Icon
  • Finally, mapped types also work on types that mix regular properties and index signatures.

  • >
    type LoginCounts = {
    Amir: number
    [userName: string]: number
    };

    const loginCounts: Partial<LoginCounts> = {
    Amir: 5,
    Betty: undefined,
    };
    loginCounts.Betty;
    Result:
    undefinedPass Icon