Please support this book: buy it or donate

14. Object.fromEntries()



This chapter explains the ES2019 feature “Object.fromEntries()” (by Darien Maillet Valentine).

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

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

  1. assert.deepEqual(
  2. Object.fromEntries([['foo',1], ['bar',2]]),
  3. {
  4. foo: 1,
  5. bar: 2,
  6. }
  7. );

It does the opposite of Object.entries():

  1. const obj = {
  2. foo: 1,
  3. bar: 2,
  4. };
  5. assert.deepEqual(
  6. Object.entries(obj),
  7. [['foo',1], ['bar',2]]
  8. );

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

14.2. Examples

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

14.2.1. _.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:

  1. const address = {
  2. street: 'Evergreen Terrace',
  3. number: '742',
  4. city: 'Springfield',
  5. state: 'NT',
  6. zip: '49007',
  7. };
  8. assert.deepEqual(
  9. pick(address, 'street', 'number'),
  10. {
  11. street: 'Evergreen Terrace',
  12. number: '742',
  13. }
  14. );

We can implement pick() as follows:

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

14.2.2. _.invert(object)

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

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

We can implement it like this:

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

14.2.3. _.mapObject(object, iteratee, context?)

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

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

This is an implementation:

  1. function mapObject(object, callback, thisValue) {
  2. const mappedEntries = Object.entries(object)
  3. .map(([key, value]) => {
  4. const mappedValue = callback.call(thisValue, value, key, object);
  5. return [key, mappedValue];
  6. });
  7. return Object.fromEntries(mappedEntries);
  8. }

14.2.4. _.findKey(object, predicate, context?)

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

  1. const address = {
  2. street: 'Evergreen Terrace',
  3. number: '742',
  4. city: 'Springfield',
  5. state: 'NT',
  6. zip: '49007',
  7. };
  8. assert.equal(
  9. findKey(address, (value, _key) => value === 'NT'),
  10. 'state'
  11. );

We can implement it as follows:

  1. function findKey(object, callback, thisValue) {
  2. for (const [key, value] of Object.entries(object)) {
  3. if (callback.call(thisValue, value, key, object)) {
  4. return key;
  5. }
  6. }
  7. return undefined;
  8. }

14.3. An implementation

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

  1. function fromEntries(iterable) {
  2. const result = {};
  3. for (const [key, value] of iterable) {
  4. let coercedKey;
  5. if (typeof key === 'string' || typeof key === 'symbol') {
  6. coercedKey = key;
  7. } else {
  8. coercedKey = String(key);
  9. }
  10. Object.defineProperty(result, coercedKey, {
  11. value,
  12. writable: true,
  13. enumerable: true,
  14. configurable: true,
  15. });
  16. }
  17. return result;
  18. }

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

14.4. A few more details about Object.fromEntries()

  • Duplicate keys: If you mention the same key multiple times, the last mention “wins”.
  1. > Object.fromEntries([['a', 1], ['a', 2]])
  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().