Objects
Objects can be used in many different ways in JavaScript. There are a number of ways to type them in order to support the different use cases.
- Exact object types: An object which has exactly a set of properties, e.g.
{a: number}
. We recommend using exact object types rather than inexact ones, as they are more precise and interact better with other type system features, like spreads. - Inexact object types: An object with at least a set of properties, but potentially other, unknown ones, e.g.
{a: number, ...}
. - Objects with indexers: An object that can used as a map from a key type to a value type, e.g.
{[string]: boolean}
. - Interfaces: Interfaces are separate from object types. Only they can describe instances of classes. E.g.
interfaces {a: number}
.
Object types try to match the syntax for objects in JavaScript as much as
possible. Using curly braces {}
and name-value pairs using a colon :
split
by commas ,
.
1const obj1: {foo: boolean} = {foo: true};2const obj2: {3 foo: number,4 bar: boolean,5 baz: string,6} = {7 foo: 1,8 bar: true,9 baz: 'three',10};
Optional object type properties
In JavaScript, accessing a property that doesn't exist evaluates to
undefined
. This is a common source of errors in JavaScript programs, so Flow
turns these into type errors.
1const obj = {foo: "bar"};2obj.bar; // Error!
2:5-2:7: Cannot get `obj.bar` because property `bar` is missing in object literal [1]. [prop-missing]
If you have an object that sometimes does not have a property you can make it
an optional property by adding a question mark ?
after the property name in
the object type.
1const obj: {foo?: boolean} = {};2
3obj.foo = true; // Works!4obj.foo = 'hello'; // Error!
4:11-4:17: Cannot assign `'hello'` to `obj.foo` because string [1] is incompatible with boolean [2]. [incompatible-type]
In addition to their set value type, these optional properties can either be
void
or omitted altogether. However, they cannot be null
.
1function acceptsObject(value: {foo?: string}) { /* ... */ }2
3acceptsObject({foo: "bar"}); // Works!4acceptsObject({foo: undefined}); // Works!5acceptsObject({}); // Works!6
7acceptsObject({foo: null}); // Error!
7:21-7:24: Cannot call `acceptsObject` with object literal bound to `value` because null [1] is incompatible with string [2] in property `foo`. [incompatible-call]
To make all properties in an object type optional, you can use the Partial
utility type:
1type Obj = {2 foo: string,3};4
5type PartialObj = Partial<Obj>; // Same as `{foo?: string}`
To make all properties in an object type required, you can use the Required
utility type:
1type PartialObj = {2 foo?: string,3};4
5type Obj = Required<PartialObj>; // Same as `{foo: string}`
Read-only object properties
You can add variance annotations to your object properties.
To mark a property as read-only, you can use the +
:
1type Obj = {2 +foo: string,3};4
5function func(o: Obj) {6 const x: string = o.foo; // Works!7 o.foo = 'hi'; // Error! 8}
7:5-7:7: Cannot assign `'hi'` to `o.foo` because property `foo` is not writable. [cannot-write]
To make all object properties in an object type read-only, you can use the $ReadOnly
utility type:
1type Obj = {2 foo: string,3};4
5type ReadOnlyObj = $ReadOnly<Obj>; // Same as `{+foo: string}`
You can also mark your properties as write-only with -
:
1type Obj = {2 -foo: string,3};4
5function func(o: Obj) {6 const x: string = o.foo; // Error! 7 o.foo = 'hi'; // Works!8}
6:23-6:25: Cannot get `o.foo` because property `foo` is not readable. [cannot-read]
Object methods
Method syntax in objects has the same runtime behavior as a function property. These two objects are equivalent at runtime:
1const a = {2 foo: function () { return 3; }3};4const b = {5 foo() { return 3; }6}
However, despite their equivalent runtime behavior, Flow checks them slightly differently. In particular, object properties written with method syntax are read-only; Flow will not allow you to write a new value to them.
1const b = {2 foo() { return 3; }3}4b.foo = () => { return 2; } // Error!
4:3-4:5: Cannot assign function to `b.foo` because property `foo` is not writable. [cannot-write]
Additionally, object methods do not allow the use of this
in their bodies, in order to guarantee simple behavior
for their this
parameters. Prefer to reference the object by name instead of using this
.
1const a = {2 x: 3,3 foo() { return this.x; } // Error! 4}5const b = {6 x: 3,7 foo(): number { return b.x; } // Works!8}
3:18-3:21: Cannot reference `this` from within method `foo` [1]. For safety, Flow restricts access to `this` inside object methods since these methods may be unbound and rebound. Consider replacing the reference to `this` with the name of the object, or rewriting the object as a class. [object-this-reference]
Object type inference
NOTE: The behavior of empty object literals has changed in version 0.191 - see this blog post for more details.
When you create an object value, its type is set at the creation point. You cannot add new properties, or modify the type of existing properties.
1const obj = {2 foo: 1,3 bar: true,4};5
6const n: number = obj.foo; // Works!7const b: boolean = obj.bar; // Works!8
9obj.UNKNOWN; // Error - prop `UNKNOWN` is not in the object value 10obj.foo = true; // Error - `foo` is of type `number`
9:5-9:11: Cannot get `obj.UNKNOWN` because property `UNKNOWN` is missing in object literal [1]. [prop-missing]10:11-10:14: Cannot assign `true` to `obj.foo` because boolean [1] is incompatible with number [2]. [incompatible-type]
If you supply a type annotation, you can add properties missing in the object value as optional properties:
1const obj: {2 foo?: number,3 bar: boolean,4} = {5 // `foo` is not set here6 bar: true,7};8
9const n: number | void = obj.foo; // Works!10const b: boolean = obj.bar; // Works!11
12if (b) {13 obj.foo = 3; // Works!14}
You can also give a wider type for a particular property:
1const obj: {2 foo: number | string,3} = {4 foo: 1,5};6
7const foo: number | string = obj.foo; // Works!8obj.foo = "hi"; // Works!
The empty object can be interpreted as a dictionary, if you supply the appropriate type annotation:
1const dict: {[string]: number} = {}; // Works!
You may need to add type annotations to an object literal, if it references itself recursively (beyond simple cases):
1const Utils = { // Error 2 foo() {3 return Utils.bar();4 },5 bar() {6 return 1;7 }8};9
10const FixedUtils = { // Works!11 foo(): number {12 return FixedUtils.bar();13 },14 bar(): number {15 return 1;16 }17};
1:7-1:11: Cannot compute a type for `Utils` because its definition includes references to itself [1]. Please add an annotation to these definitions [2] [3] [recursive-definition]
Exact and inexact object types
Exact object types are the default (as of version 0.202), unless you have set exact_by_default=false
in your .flowconfig
.
Inexact objects (denoted with the ...
) allow extra properties to be passed in:
1function method(obj: {foo: string, ...}) { /* ... */ }2
3method({foo: "test", bar: 42}); // Works!
Note: This is because of "width subtyping".
But exact object types do not:
1function method(obj: {foo: string}) { /* ... */ }2
3method({foo: "test", bar: 42}); // Error!
3:8-3:29: Cannot call `method` with object literal bound to `obj` because property `bar` is missing in object type [1] but exists in object literal [2]. [prop-missing]
If you have set exact_by_default=false
, you can denote exact object types by adding a pair of "vertical bars" or "pipes" to the inside of the curly braces:
1const x: {|foo: string|} = {foo: "Hello", bar: "World!"}; // Error!
1:28-1:56: Cannot assign object literal to `x` because property `bar` is missing in object type [1] but exists in object literal [2]. [prop-missing]
Intersections of exact object types may not work as you expect. If you need to combine exact object types, use object type spread:
1type FooT = {foo: string};2type BarT = {bar: number};3
4type FooBarT = {...FooT, ...BarT};5const fooBar: FooBarT = {foo: '123', bar: 12}; // Works!6
7type FooBarFailT = FooT & BarT;8const fooBarFail: FooBarFailT = {foo: '123', bar: 12}; // Error!
8:33-8:53: Cannot assign object literal to `fooBarFail` because property `bar` is missing in `FooT` [1] but exists in object literal [2]. [prop-missing]8:33-8:53: Cannot assign object literal to `fooBarFail` because property `foo` is missing in `BarT` [1] but exists in object literal [2]. [prop-missing]
Object type spread
Just like you can spread object values, you can also spread object types:
1type ObjA = {2 a: number,3 b: string,4};5
6const x: ObjA = {a: 1, b: "hi"};7
8type ObjB = {9 ...ObjA,10 c: boolean,11};12
13const y: ObjB = {a: 1, b: 'hi', c: true}; // Works!14const z: ObjB = {...x, c: true}; // Works!
You have to be careful spreading inexact objects. The resulting object must also be inexact, and the spread inexact object may have unknown properties that can override previous properties in unknown ways:
1type Inexact = {2 a: number,3 b: string,4 ...5};6
7type ObjB = { // Error! 8 c: boolean, 9 ...Inexact, 10}; 11
12const x: ObjB = {a:1, b: 'hi', c: true};
7:13-10:1: Flow cannot determine a type for object type [1]. `Inexact` [2] is inexact, so it may contain `c` with a type that conflicts with `c`'s definition in object type [1]. Try making `Inexact` [2] exact. [cannot-spread-inexact]9:6-9:12: inexact `Inexact` [1] is incompatible with exact object type [2]. [incompatible-exact]
The same issue exists with objects with indexers, as they also have unknown keys:
1type Dict = {2 [string]: number,3};4
5type ObjB = { // Error! 6 c: boolean, 7 ...Dict, 8}; 9
10const x: ObjB = {a: 1, b: 2, c: true};
5:13-8:1: Flow cannot determine a type for object type [1]. `Dict` [2] cannot be spread because the indexer string [3] may overwrite properties with explicit keys in a way that Flow cannot track. Try spreading `Dict` [2] first or remove the indexer. [cannot-spread-indexer]
Spreading an object value at runtime only spreads "own" properties, that is properties that are on the object directly, not the prototype chain. Object type spread works in the same way. Because of this, you can't spread interfaces, as they don't track whether a property is "own" or not:
1interface Iface {2 a: number;3 b: string;4}5
6type ObjB = { // Error! 7 c: boolean, 8 ...Iface, 9}; 10
11const x: ObjB = {a: 1, b: 'hi', c: true};
6:13-9:1: Flow cannot determine a type for object type [1]. `Iface` [2] cannot be spread because interfaces do not track the own-ness of their properties. Try using an object type instead. [cannot-spread-interface]
Objects as maps
JavaScript includes a Map
class,
but it is still very common to use objects as maps as well. In this use case, an object
will likely have properties added to it and retrieved throughout its lifecycle.
Furthermore, the property keys may not even be known statically, so writing out
a type annotation would not be possible.
For objects like these, Flow provides a special kind of property, called an "indexer property." An indexer property allows reads and writes using any key that matches the indexer key type.
1const o: {[string]: number} = {};2o["foo"] = 0;3o["bar"] = 1;4const foo: number = o["foo"];
An indexer can be optionally named, for documentation purposes:
1const obj: {[user_id: number]: string} = {};2obj[1] = "Julia";3obj[2] = "Camille";4obj[3] = "Justin";5obj[4] = "Mark";
When an object type has an indexer property, property accesses are assumed to have the annotated type, even if the object does not have a value in that slot at runtime. It is the programmer's responsibility to ensure the access is safe, as with arrays.
1const obj: {[number]: string} = {};2obj[42].length; // No type error, but will throw at runtime
Indexer properties can be mixed with named properties:
1const obj: {2 size: number,3 [id: number]: string4} = {5 size: 06};7
8function add(id: number, name: string) {9 obj[id] = name;10 obj.size++;11}
You can mark an indexer property as read-only (or write-only) using variance annotations:
1type ReadOnly = {+[string]: number};2type WriteOnly = {-[string]: number};
Keys, values, and indexed access
You can extract the keys of an object type using the $Keys
utility type:
1type Obj = {2 foo: string,3 bar: number,4};5
6type T = $Keys<Obj>;7
8function acceptsKeys(k: T) { /* ... */ }9
10acceptsKeys('foo'); // Works!11acceptsKeys('bar'); // Works!12acceptsKeys('hi'); // Error!
12:13-12:16: Cannot call `acceptsKeys` with `'hi'` bound to `k` because property `hi` is missing in `Obj` [1]. [prop-missing]
You can extract the values of an object type using the $Values
utility type:
1type Obj = {2 foo: string,3 bar: number,4};5
6type T = $Values<Obj>;7
8function acceptsValues(v: T) { /* ... */ }9
10acceptsValues(2); // Works!11acceptsValues('hi'); // Works!12acceptsValues(true); // Error!
12:15-12:18: Cannot call `acceptsValues` with `true` bound to `v` because: [incompatible-call] Either boolean [1] is incompatible with string [2]. Or boolean [1] is incompatible with number [3].
You can get the type of an object type's specific property using indexed access types:
1type Obj = {2 foo: string,3 bar: number,4};5
6type T = Obj['foo'];7
8function acceptsStr(x: T) { /* ... */ }9
10acceptsStr('hi'); // Works!11acceptsStr(1); // Error!
11:12-11:12: Cannot call `acceptsStr` with `1` bound to `x` because number [1] is incompatible with string [2]. [incompatible-call]
Arbitrary objects
If you want to accept an arbitrary object safely, there are a couple of patterns you could use.
An empty inexact object {...}
accepts any object:
1function func(obj: {...}) {2 // ...3}4
5func({}); // Works!6func({a: 1, b: "foo"}); // Works!
It's often the right choice for a generic bounded to accept any object:
1function func<T: {...}>(obj: T) {2 // ...3}4
5func({}); // Works!6func({a: 1, b: "foo"}); // Works!
However, you can't access any properties off of {...}
.
You can also try using a dictionary with mixed
values, which would allow you to access any property (with a resulting mixed
type):
1function func(obj: {+[string]: mixed}) {2 const x: mixed = obj['bar'];3}4
5func({}); // Works!6func({a: 1, b: "foo"}); // Works!
The type Object
is just an alias for any
, and is unsafe.
You can ban its use in your code with the unclear-type lint.