Portrait Dr. Axel Rauschmayer
Dr. Axel Rauschmayer
Homepage | Twitter
Cover of book “Exploring ES6”
Book, exercises, quizzes
(free to read online)
Logo of newsletter “ES.next news”
Newsletter (free)
Cover of book “JavaScript for impatient programmers”
Book (free online)

ES proposal: Object.fromEntries()

[2019-01-28] dev, javascript, es2019
(Ad, please don’t block)

The proposal “Object.fromEntries” (by Darien Maillet Valentine, Jordan Harband and Kevin Gibbons) is at stage 4 and therefore part of ECMAScript 2019. This blog post explains how it works.

Object.fromEntries() vs. Object.entries()  

Given an iterable over [key,value] pairs, Object.fromEntries() creates an object:

assert.deepEqual(
  Object.fromEntries([['foo',1], ['bar',2]]),
  {
    foo: 1,
    bar: 2,
  }
);

It does the opposite of Object.entries():

const obj = {
  foo: 1,
  bar: 2,
};
assert.deepEqual(
  Object.entries(obj),
  [['foo',1], ['bar',2]]
);

Combining Object.entries() with Object.fromEntries() helps with implementing a variety of operations related to objects. Read on for examples.

Examples  

In this section, we’ll use Object.entries() and Object.fromEntries() to implement several tool functions from the library Underscore.

_.pick(object, ...keys)  

pick() removes all properties from object whose keys are not among keys. The removal is non-destructive: pick() creates a modified copy and does not change the original. For example:

const address = {
  street: 'Evergreen Terrace',
  number: '742',
  city: 'Springfield',
  state: 'NT',
  zip: '49007',
};
assert.deepEqual(
  pick(address, 'street', 'number'),
  {
    street: 'Evergreen Terrace',
    number: '742',
  }
);

We can implement pick() as follows:

function pick(object, ...keys) {
  const filteredEntries = Object.entries(object)
    .filter(([key, _value]) => keys.includes(key));
  return Object.fromEntries(filteredEntries);
}

_.invert(object)  

invert() non-destructively swaps the keys and the values of an object:

assert.deepEqual(
  invert({a: 1, b: 2, c: 3}),
  {1: 'a', 2: 'b', 3: 'c'}
);

We can implement it like this:

function invert(object) {
  const mappedEntries = Object.entries(object)
    .map(([key, value]) => [value, key]);
  return Object.fromEntries(mappedEntries);
}

_.mapObject(object, iteratee, context?)  

mapObject() is like the Array method .map(), but for objects:

assert.deepEqual(
  mapObject({x: 7, y: 4}, value => value * 2),
  {x: 14, y: 8}
);

This is an implementation:

function mapObject(object, callback, thisValue) {
  const mappedEntries = Object.entries(object)
    .map(([key, value]) => {
      const mappedValue = callback.call(thisValue, value, key, object);
      return [key, mappedValue];
    });
  return Object.fromEntries(mappedEntries);
}

_.findKey(object, predicate, context?)  

findKey() returns the key of the first property for which predicate returns true:

const address = {
  street: 'Evergreen Terrace',
  number: '742',
  city: 'Springfield',
  state: 'NT',
  zip: '49007',
};
assert.equal(
  findKey(address, (value, _key) => value === 'NT'),
  'state'
);

We can implement it as follows:

function findKey(object, callback, thisValue) {
  for (const [key, value] of Object.entries(object)) {
    if (callback.call(thisValue, value, key, object)) {
      return key;
    }
  }
  return undefined;
}

An implementation  

Object.fromEntries() could be implemented as follows (I’ve omitted a few checks):

function fromEntries(iterable) {
  const result = {};
  for (const [key, value] of iterable) {
    let coercedKey;
    if (typeof key === 'string' || typeof key === 'symbol') {
      coercedKey = key;
    } else {
      coercedKey = String(key);
    }
    Object.defineProperty(result, coercedKey, {
      value,
      writable: true,
      enumerable: true,
      configurable: true,
    });
  }
  return result;
}

The official polyfill is available via the npm package object.fromentries.

A few more details about Object.fromEntries()  

  • Duplicate keys: If you mention the same key multiple times, the last mention “wins”.
    > Object.fromEntries([['a', 1], ['a', 2]])
    { a: 2 }
    
  • Symbols as keys: Even though Object.entries() ignores properties whose keys are symbols, Object.fromEntries() accepts symbols as keys.
  • Coercion of keys: The keys of the [key,value] pairs are coerced to property keys: Values other than strings and symbols are coerced to strings.
  • Iterables vs. Arrays:
    • Object.entries() returns an Array (which is consistent with Object.keys() etc.). Its [key,value] pairs are 2-element Arrays.
    • Object.fromEntries() is flexible: It accepts iterables (which includes Arrays and is consistent with new Map() etc.). Its [key,value] pairs are only required to be objects that have properties with the keys '0' and '1' (which includes 2-element Arrays).
  • Only enumerable data properties are supported: If you want to create non-enumerable properties and/or non-data properties, you need to use Object.defineProperty() or Object.defineProperties().