Flow tips and tricks, part 3: Object methods
Problem
With the rise of ES2016 and functional programming in JavaScript, we tend to use frequently two methods of Object
: Object.values and Object.entries.
But apparently there’s an issue that many of us have run through when using these methods with Flow.
Imagine that for some reason, instead of having an array containing items of a same type, we have an object that contains these items (which is quite possible in case we want to get a value by referencing its key in the object). An example for this would be :
/* @flow */
type Profession = {
title: string,
salary: number,
};
type People = { [title: string]: Profession };
const people: People = {
Thomas: {
title: 'Teacher',
salary: 10000,
},
Kenzo: {
title: 'Engineer',
salary: 20000,
},
};
const salaries: Array<number> = Object.values(people).map(person => {
return person.salary;
});
We will get an error from Flow saying that:
And if we want to iterate this people
object by using Object.entries
:
const descriptions: Array<string> = Object.entries(people).map(([name, person]) => {
return `name : ${name}, salary: ${person.salary}`;
});
Flow also warns us about:
I saw some workaround examples which don’t use Object.values
or Object.entries
but the old classic method Object.keys as follows:
const details: Array<Profession> = Object.keys(people).map(name => people[name]);
and no Flow errors :
No errors!
Ok so what’s the problem? How do we type it correctly? As a novice JavaScript developer, it will take some time to tear the hair to find out a solution. I will explain why we got these Flow errors and why the Object.keys
workaround isn’t a truly sound solution.
Explanation
Here is an extract from core.js of Flow project:
declare class Object {
...
static entries(object: any): Array<[string, mixed]>;
...
static values(object: any): Array<mixed>;
}
Let’s take a simple example. From Flow documentation about Width Subtyping, having an object person
with the type type Person = { name: string }
means that person.name
has the type string
. But it cannot prevent person
from having other properties.
/* @flow */
type Person = { name: string };
const person: Person = {
name: 'Thierry',
age: 20,
};
In this case, Object.values(person)
will return ["Thierry", 20]
which is an array of a string
and a number
. And these aren’t the same type which means they don’t have the same properties and methods. So that explains the extracted code above about the result as Array<mixed>
type to be able to handle all possible cases.
With the same example, we would get a surprise if we expected to have an array of string
by doing:
const names: Array<string> = Object.keys(person).map(name => person[name]);
Flow wouldn’t warn us about any error but we would possibly get one at runtime by presuming that all elements in names
are string
and doing some string operation as usual. As a result, Object.keys
isn’t a truly sound workaround.
In practice
At the present (Flow version 0.74), the Flow team are working on it this issue, but it seems to be a long way to get to a final solution. In practice, we have nearly two possibilities:
- Using the
Object.keys
workaround. - Using
Object.values
orObject.entries
+any
casting :
const salaries: Array<number> = Object.values(people).map((person: any) => person.salary);
If you don’t like any
, we can use $FlowFixMe
option (details here, you can find an example of $FlowFixMe
implementation in .flowconfig
of React-Native project)
I have a personal preference on using $FlowFixMe
or any
casting even if it is not safe. But this is one of the rare places I use $FlowFixMe
or any
while waiting for a better improvement from Flow team. Once having a solution, just come back and remove all $FlowFixMe
or any
.