Skip to main content

Mapped Types

Flow's mapped types allow you to transform object types. They are useful for modeling complex runtime operations over objects.

Basic Usage

Mapped Types have syntax similar to indexed object types but use the in keyword:

1type O = {foo: number, bar: string};2
3type Methodify<T> = () => T;4
5type MappedType = {[key in keyof O]: Methodify<O[key]>};

In this example, MappedType has all of the keys from O with all of the value types transformed by Methoditfy<O[key]>. The key variable is substituted for each key in O when creating the property, so this type evaluates to:

{
foo: Methodify<O['foo']>,
bar: Methodify<O['bar']>,
}
= {
foo: () => number,
bar: () => string,
}

Mapped Type Sources

We call the type that comes after the in keyword the source of the mapped type. The source of a mapped type must be a subtype of string | number | symbol:

1type MappedType = {[key in boolean]: number}; // ERROR!
1:28-1:34: Cannot instantiate mapped type [1] because boolean [2] is incompatible with `string | number | symbol`, so it cannot be used to generate keys for mapped type [1]. [incompatible-type]

Typically, you'll want to create a mapped type based on another object type. In this case, you should write your mapped type using an inline keyof:

1type GetterOf<T> = () => T;2type Obj = {foo: number, bar: string};3type MappedObj = {[key in keyof Obj]: GetterOf<Obj[key]>};

NOTE: keyof only works inline in mapped types for now. Full support for keyof is not yet available.

But you do not need to use an object to generate a mapped type. You can also use a union of string literal types to represent the keys of an object type:

1type Union = 'foo' | 'bar' | 'baz';2type MappedType = {[key in Union]: number};3// = {foo: number, bar: number, baz: number};

Importantly, when using string literals the union is collapsed into a single object type:

1type MappedTypeFromKeys<Keys: string> = {[key in Keys]: number};2type MappedFooAndBar = MappedTypeFromKeys<'foo' | 'bar'>;3// = {foo: number, bar: number}, not {foo: number} | {bar: number}

If you use a type like number or string in the source of your mapped type then Flow will create an indexer:

1type MappedTypeFromKeys<Keys: string> = {[key in Keys]: number};2type MappedFooAndBarWithIndexer = MappedTypeFromKeys<'foo' | 'bar' | string>;3// = {foo: number, bar: number, [string]: number}

Distributive Mapped Types

When the mapped type uses an inline keyof or a type parameter bound by a $Keys Flow will distribute the mapped type over unions of object types.

For example:

1// This mapped type uses keyof inline2type MakeAllValuesNumber<O: {...}> = {[key in keyof O]: number};3type ObjWithFoo = {foo: string};4type ObjWithBar = {bar: string};5
6type DistributedMappedType = MakeAllValuesNumber<7  | ObjWithFoo8  | ObjWithBar9>; // = {foo: number} | {bar: number};10
11// This mapped type uses a type parameter bound by $Keys12type Pick<O: {...}, Keys: $Keys<O>> = {[key in Keys]: O[key]};13type O1 = {foo: number, bar: number};14type O2 = {bar: string, baz: number};15type PickBar = Pick<O1 | O2, 'bar'>; // = {bar: number} | {bar: string};

Distributive mapped types will also distribute over null and undefined:

1type Distributive<O: ?{...}> = {[key in keyof O]: O[key]};2type Obj = {foo: number};3type MaybeMapped = Distributive<?Obj>;// = ?{foo: number}4null as MaybeMapped; // OK5undefined as MaybeMapped; // OK6({foo: 3}) as MaybeMapped; // OK

Property Modifiers

You can also add + or - variance modifiers and the optionality modifier ? in mapped types:

1type O = {foo: number, bar: string}2type ReadOnlyPartialO = {+[key in keyof O]?: O[key]}; // = {+foo?: number, +bar?: string};

When no variance nor optionality modifiers are provided and the mapped type is distributive, the variance and optionality are determined by the input object:

1type O = {+foo: number, bar?: string};2type Mapped = {[key in keyof O]: O[key]}; // = {+foo: number, bar?: string}

Otherwise, the properties are read-write and required when no property modifiers are present:

1type Union = 'foo' | 'bar' | 'baz';2type MappedType = {[key in Union]: number};3// = {foo: number, bar: number, baz: number};

NOTE: Flow does not yet support removing variance or optionality modifiers.

Mapped Type on Arrays 0.246

Mapped type also works on array or tuple inputs. If the mapped type is in the form of

{[K in keyof <type_1>]: <type_2>}

then type_1 is allowed to be an array or tuple type.

This feature will be especially useful if you want to map over elements of a tuple:

1type Tuple = [+a: number, b?: string];2type MappedTuple = {[K in keyof Tuple]: Tuple[K] extends number ? boolean : string};3const a: MappedTuple[0] = true;4const b: MappedTuple[1] = '';5'' as MappedTuple[0] // error
6false as MappedTuple[1] // error
7declare const mapped: MappedTuple;8mapped[0] = true; // error: cannot-write
5:1-5:2: Cannot cast empty string to indexed access because string [1] is incompatible with boolean [2]. [incompatible-cast]
6:1-6:5: Cannot cast `false` to indexed access because boolean [1] is incompatible with string [2]. [incompatible-cast]
8:1-8:9: Cannot assign `true` to `mapped[0]` because tuple element at index `0` [1] labeled `a` is not writable. [cannot-write]

For now, the only supported property modifier on array input is the optionality modifier ?.

1type Tuple = [+a: number, b?: string];2type Supported = {[K in keyof Tuple]?: string};3type Unsupported1 = {+[K in keyof Tuple]: string};
4type Unsupported2 = {-[K in keyof Tuple]: string};
3:21-3:49: Mapped Types do not yet support variance annotations on array inputs. [invalid-mapped-type]
4:21-4:49: Mapped Types do not yet support variance annotations on array inputs. [invalid-mapped-type]

Adoption

To use mapped types, you need to upgrade your infrastructure so that it supports the syntax:

  • flow and flow-parser: 0.210.0. Between v0.210.0 to v0.211.1, you need to explicitly enable it in your .flowconfig, under the [options] heading, add mapped_type=true.
  • prettier: 3
  • babel with babel-plugin-syntax-hermes-parser. See our Babel guide for setup instructions.
  • eslint with hermes-eslint. See our ESLint guide for setup instructions.