JavaScript: Mapping objects

📄 Wiki page | 🕑 Last updated: Feb 9, 2023

JavaScript 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 have any suggestions or questions (related to this or any other topic), feel free to contact me. ℹī¸


If you find this site useful in any way, please consider supporting it.