ES2019: `Object.fromEntries()`
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 withObject.keys()etc.). Its [key,value] pairs are 2-element Arrays.Object.fromEntries()is flexible: It accepts iterables (which includes Arrays and is consistent withnew 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()orObject.defineProperties().