How to iterate easily over object properties in JavaScript

I like my code to be elegant and efficient. The logic should be straightforward to make it hard for bugs to hide, the dependencies minimal to ease maintenance [...]. Clean code does one thing well.

Bjarne Stroustrup, inventor of C++

In the same period of ECMAScript 2016 release in June 2016, JavaScript developers are happy to know that another bunch of awesome proposals reached the stage 4 (finished).

Let's list these features:

The new proposals are included in the ECMAScript 2017 standard, which probably will be released in the summer of 2017. Notice that the list of features may grow until that time. That's great!

Of course, you don't have to wait for ES2017 release or until the new features are implemented by vendors! Babel already contains most of these finished proposals.

This article is focused on how to improve the iteration over object properties:

  • To get properties values using Object.values()
  • To get properties key/value pairs using Object.entries()

At first sight, these static functions don't seem to add significant value. But when they're combined with destructuring assignments and for..of loops, you get a short and sweet way to iterate over object's properties.
Let's dive in.

1. Own and enumerable properties

As you might knew already, Object.key() accesses only object's own and enumerable properties. It is reasonable, since most of the times only these kind of properties need evaluation.

Let's see an example when an object has own and inherited properties. Object.key() returns only own property keys:

let simpleColors = {  
  colorA: 'white',
  colorB: 'black'
let natureColors = {  
  colorC: 'green',
  colorD: 'yellow'
Object.setPrototypeOf(natureColors, simpleColors);  
Object.keys(natureColors); // => ['colorC', 'colorD']  
natureColors['colorA'];    // => 'white'  
natureColors['colorB'];    // => 'black'  

Object.keys(natureColors) returns own and enumerable property keys of the natureColors object: ['colorC', 'colorD'].
natureColors contains the properties inherited from simpleColors prototype object. However Object.keys() function skips them.

Object.values() and Object.entries() access object's properties by the same criteria: own and enumerable properties. Let's take a look:

// ...
// => ['green', 'yellow']
// => [ ['colorC', 'green'], ['colorD', 'yellow'] ]

Now notice the difference from loop statement. The loop iterates over enumerable, own and inherited properties. The following example illustrates that:

// ...
let enumerableKeys = [];  
for (let key in natureColors) {  
enumerableKeys; // => ['colorC', 'colorD', 'colorA', 'colorB']  

enumerableKeys array contains natureColors own properties keys: 'colorC' and 'colorD'.
Additionally iterated over the property keys inherited from simpleColors prototype object: 'colorA' and 'colorB'.

2. Object.values() returns property values

To distinguish the benefits of Object.values() usage, let's see how to get object's property values in a pre-ES2017 way.

First the property keys are collected with Object.keys(). Then a property accessor is used and the value is stored in an additional variable. Let's see an example:

let meals = {  
  mealA: 'Breakfast',
  mealB: 'Lunch',
  mealC: 'Dinner'
for (let key of Object.keys(meals)) {  
  let mealName = meals[key];
  // ... do something with mealName
// 'Breakfast' 'Lunch' 'Dinner'

meals is a regular plain JavaScript object. The object keys are taken using Object.keys(meals) and in a for..of loop enumerated.
The code looks pretty simple, however it can be optimized by removing the line let mealName = meals[key].

The optimization is possible by applying a direct access to object property values using Object.values(). Now the property accessor line can be removed:

let meals = {  
  mealA: 'Breakfast',
  mealB: 'Lunch',
  mealC: 'Dinner'
for (let mealName of Object.values(meals)) {  
// 'Breakfast' 'Lunch' 'Dinner'

Because Object.values(meals) returns the object property values in an array, the whole task reduces to a compact for..of loop. mealName is assigned directly in the loop, so there is no need for the additional line, like it was in the previous example.

Object.values() does one thing, but does it well. This is a true path to clean code.

3. Object.entries() returns pairs of property values and keys

Object.entries() goes beyond and returns an array of object's property values and keys in pairs: [ [key1, value1], [key2, value2], ..., [keyN, valueN] ].

Probably it's not comfortable to use these pairs directly. Fortunately the array destructuring assignment let [x, y] = array in a for..of loop makes it really easy to access the key and value.

The following example shows Object.entries() in action:

let meals = {  
  mealA: 'Breakfast',
  mealB: 'Lunch',
  mealC: 'Dinner'
for (let [key, value] of Object.entries(meals)) {  
  console.log(key + ':' + value);
// 'mealA:Breakfast' 'mealB:Lunch' 'mealC:Dinner'

Object.entries(meals) returns meal object's pairs of keys and values in an array.
On the left side of for..of loop, the array destructuring assignment let [key, value] assigns key and value variables.

As seen, accessing keys and values has now a comfortable and easy to understand form. No additional lines for assignments or declarations are necessary, since the Object.entries() returns a collection compatible with array destructuring assignment.

Object.entries() is helpful when a plain object should be imported into a Map. The problem becomes trivial to solve, because Object.entries() returns a format exactly that Map constructor accepts: key and value pairs.

Let's create a JavaScript object and export it into a Map:

let greetings = {  
  morning: 'Good morning',
  midday: 'Good day',
  evening: 'Good evening'
let greetingsMap = new Map(Object.entries(greetings));  
greetingsMap.get('morning'); // => 'Good morning'  
greetingsMap.get('midday');  // => 'Good day'  
greetingsMap.get('evening'); // => 'Good evening'  

new Map(Object.entries(greetings)) constructor is invoked with an argument that is an array of key and value pairs, exported from greetings object.
As expected the map instance greetingsMap contains properties imported from greetings object. These can be accessed using .get(key) method.

Interestingly that Map provides equivalent to Object.values() and Object.entries() methods (only that they return Iterators), in order to extract property values or key-value pairs for a map instance:

Maps are an improved version of plain objects. You can get the size of a map (for a plain object you have to do it manually) and use as key any object type (plain object uses as key a string primitive type).

Let's see what return .values() and .entries() map's methods:

// ...
// => ['Good morning', 'Good day', 'Good evening']
// => [ ['morning', 'Good morning'], ['midday', 'Good day'], 
//      ['evening', 'Good evening'] ]

Notice that greetingsMap.values() and greetingsMap.entries() return iterator objects. To put the result into an array, the spread operator ... is necessary.
In a for..of loop statement the iterator can be used directly.

4. A note on ordering

JavaScript objects are simple key-values maps. So the order of properties in the object is insignificant. You should not rely on it in most of the cases.

However ES2015 has standardized the way properties are iterated: first come ordered number strings, then strings by insert order and then symbols by insert order. In ES5 and earlier standards, the order of properties is simply not specified.

If you need an ordered collection, storing data into an Array or Set is the recommend way.

5. Conclusion

Object.values() and Object.entries() are another improvement step for providing JavaScript developers with new standardized helper functions.

Object.entries() works best with array destructuring assignments, in a way that key and value are assigned to different variables easily. This function also makes easy to export a plain JavaScript object properties into a Map object. Maps have better support of the traditional map (or hash) behavior.

Notice that the order in which Object.values() and Object.entries() return data is undetermined. So do not rely on the order.

I hope you enjoyed the reading! If so, feel free to share the post and write your opinion about these features in the comments bellow!

Show Comments
comments powered by Disqus