JavaScript: Mapping objects
đ Wiki page | đ Last updated: Feb 9, 2023JavaScript arrays have a convenient map()
function on their prototype:
let a = [4, 2]
let f = x => x * x
a.map(f) // [16, 4]
On the other hand, if we have our data in plain objects (which is the most used dict-like structure in JS), we don't have that convenience:
let o = {a: 4, b: 2}
let f = x => x * x
We can't call o.map()
because map
doesn't exist on Object.prototype
(and adding it there would be a bad idea since all objects would inherit that function).
We also don't have Object.map()
, but we have Object.keys()
, which gives us an array that we can reduce into an object.
Functional approach
A purely functional version could look like this:
Object.keys(o).reduce((m, k) => ({...m, [k]: o[k] * o[k]}, m), {})
A semi-functional version (by mutating the intermediate value):
Object.keys(o).reduce((m, k) => Object.assign(m, {[k] : o[k] * o[k]}), {})
We can remove Object.assign
and modify the m
variable in place:
Object.keys(o).reduce((m, k) => (m[k] = o[k] * o[k], m), {})
Since ES2017, we can use Object.entries
instead of Object.keys
to get the key and value as a pair:
Object.entries(o).reduce((m, [k, v]) => (m[k] = v * v, m), {})
Generic map function
It's very useful to extract this functionality into a generic function. Our last example could look like this in the form of a function:
function map(f, o) {
return Object.entries(o).reduce((m, [k, v]) => (m[k] = f(v,k), m), {})
}
map(x => x * x, {a: 4, b: 2}) // {a: 16, b: 4}
Note: We're taking the object as a last argument so we can easily curry our function (this is often called data-last approach). More on that later.
Array support
One of the advantages of standalone functions is that we can handle different data types with the same function. For example, we can add support for arrays:
function map(f, o) {
if(Array.isArray(o)) return o.map(f)
return Object.entries(o).reduce((m, [k, v]) => (m[k] = f(v,k), m), {})
}
Now it works transparently for both objects and arrays:
map(x => x * x, {a: 4, b: 2}) // {a: 16, b: 4}
map(x => x * x, [4, 2]) // [16, 4]
Imperative approach
Since the functional approach in JavaScript has some overhead, here's also (a bit faster) imperative version:
function map(f, o) {
if(Array.isArray(o)) return o.map(f)
let m = {}
for(let k in o) {
if(o.hasOwnProperty(k))
m[k] = f(o[k], k)
}
return m
}
I'll include benchmarks, but usually even functional versions are fast enough.
Ask me anything / Suggestions
If you find this site useful in any way, please consider supporting it.