Mapped Types
Mapped types transform object types by iterating over their keys and producing new property types.
type O = {foo: number, bar: string};
type ReadOnly = {readonly [key in keyof O]: O[key]}; Mapped types match TypeScript including distributivity, keyof-based source iteration, and the homomorphic {[K in keyof T]: ...} form.
When to use this
Use mapped types when you need to systematically transform every property of an existing object type — for example, making all properties optional, read-only, or wrapping their values. If you only need to change a few specific properties, object type spread is simpler.
Basic Usage
Mapped types have syntax similar to indexed object types but use the in keyword:
type O = {foo: number, bar: string};
type Methodify<T> = () => T;
type 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:
type MappedType = {[key in boolean]: number}; // ERROR! 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:
type GetterOf<T> = () => T;
type Obj = {foo: number, bar: string};
type MappedObj = {[key in keyof Obj]: GetterOf<Obj[key]>}; 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:
type Union = 'foo' | 'bar' | 'baz';
type MappedType = {[key in Union]: number};
// = {foo: number, bar: number, baz: number}; Importantly, when using string literals the union is collapsed into a single object type:
type MappedTypeFromKeys<Keys extends string> = {[key in Keys]: number};
type MappedFooAndBar = MappedTypeFromKeys<'foo' | 'bar'>;
// = {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:
type MappedTypeFromKeys<Keys extends string> = {[key in Keys]: number};
type MappedFooAndBarWithIndexer = MappedTypeFromKeys<'foo' | 'bar' | string>;
// = {foo: number, bar: number, [string]: number} Distributive Mapped Types
When the mapped type uses an inline keyof or a type parameter bound by keyof Flow will distribute the mapped type over unions of object types.
For example:
// This mapped type uses keyof inline
type MakeAllValuesNumber<O extends {...}> = {[key in keyof O]: number};
type ObjWithFoo = {foo: string};
type ObjWithBar = {bar: string};
type DistributedMappedType = MakeAllValuesNumber<
| ObjWithFoo
| ObjWithBar
>; // = {foo: number} | {bar: number};
// This mapped type uses a type parameter bound by keyof
type Pick<O extends {...}, Keys extends keyof O> = {[key in Keys]: O[key]};
type O1 = {foo: number, bar: number};
type O2 = {bar: string, baz: number};
type PickBar = Pick<O1 | O2, 'bar'>; // = {bar: number} | {bar: string}; Distributive mapped types will also distribute over null and undefined:
type Distributive<O extends ?{...}> = {[key in keyof O]: O[key]};
type Obj = {foo: number};
type MaybeMapped = Distributive<?Obj>;// = ?{foo: number}
null as MaybeMapped; // OK
undefined as MaybeMapped; // OK
({foo: 3}) as MaybeMapped; // OK Property Modifiers
Adding variance and optionality
You can add readonly or writeonly variance modifiers and the optionality modifier ? in mapped types:
type O = {foo: number, bar: string}
type ReadOnlyPartialO = {readonly [key in keyof O]?: O[key]}; // = {readonly foo?: number, readonly bar?: string}; Removing optionality with -?
The ? modifier makes every property optional. Prefixing it with - does the opposite: -? removes optionality, turning every optional property into a required one. This works like the Required<T> utility type.
type O = {foo: number, bar?: string};
type RequiredO = {[key in keyof O]-?: O[key]}; // = {foo: number, bar: string}
({foo: 1}) as RequiredO; // ERROR: bar is now required -? only clears the optionality of properties that were optional in the source. It does not strip void or undefined from a value type that was already required:
type T = {a: string | void};
type Mapped = {[K in keyof T]-?: T[K]};
declare const m: Mapped;
m.a as string | void; // OK: `a` was not optional, so its value type is unchanged
m.a as string; // ERROR: void is still part of the value type -? also works on tuple inputs, where it makes elements no longer optional:
type Tuple = [a: string, b?: number, c?: boolean];
type AllRequired = {[K in keyof Tuple]-?: Tuple[K]};
(['']) as AllRequired; // ERROR: elements 1 and 2 are now required
declare const t: AllRequired;
t as [string, number, boolean]; // OK Note: Flow does not yet support
+readonly/-readonlyvariance modifiers. They are coming soon.
Mapped Type on Arrays
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:
type Tuple = [readonly a: number, b?: string];
type MappedTuple = {[K in keyof Tuple]: Tuple[K] extends number ? boolean : string};
const a: MappedTuple[0] = true;
const b: MappedTuple[1] = '';
'' as MappedTuple[0] // error
false as MappedTuple[1] // error
declare const mapped: MappedTuple;
mapped[0] = true; // error: cannot-write For now, the only supported property modifier on array input is the optionality modifier ?.
type Tuple = [readonly a: number, b?: string];
type Supported = {[K in keyof Tuple]?: string};
type Unsupported1 = {readonly [K in keyof Tuple]: string}; // Error
type Unsupported2 = {writeonly [K in keyof Tuple]: string}; // Error Adoption
To use mapped types, you need to upgrade your infrastructure so that it supports the syntax:
-
prettier: 3 -
babelwithflow-parser/babel-plugin. See our Babel guide for setup instructions. -
eslintwithflow-eslint. See our ESLint guide for setup instructions.
See Also
- Indexed Access Types — extracting property types, commonly used in mapped type bodies
- Conditional Types — another advanced type feature for type-level logic
- Generics — mapped types rely on generic type parameters
-
Utility Types — built-in types like
Partial,Readonly, andPick(which mapped types generalize) -
Modernizing Legacy Flow Syntax — migrating the legacy
$ObjMap/$TupleMaputilities that mapped types replace
© 2013–present Facebook Inc.
Licensed under the MIT License.
https://flow.org/en/docs/types/mapped-types