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 forkeyof
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
andflow-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, addmapped_type=true
.prettier
: 3babel
withbabel-plugin-syntax-hermes-parser
. See our Babel guide for setup instructions.eslint
withhermes-eslint
. See our ESLint guide for setup instructions.