Flow tips and tricks, part 3: Object methods

NGUYEN Trung
3 min readJun 10, 2018

--

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 namesare 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 or Object.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.

--

--