Back to All Posts

Creating a .map() Method for Objects, Strings, Sets, and Maps

It’s a well-established truth of the universe that JavaScript’s Array.prototype.map() is one of the best parts of the language, allowing us to write cleaner, simpler code to manipulate array values, instead of using something like forEach().

For example, let’s say we want to create a copy of an array with values that are tripled.

The Uncool Way

let someNumz = [2, 3, 5, 4, 5];
let someTripledNumz = [];

someNumz.forEach((value, index) => {
  someTripledNumz.push(value * 3);
});

console.log(someNumz); //-- [2, 3, 5, 4, 5]
console.log(someTripledNumz); //-- [6, 9, 15, 12, 15]

That works, but it’s rough around the edges. We need to declare an empty someTripledNumz array above, and then go through the process of tripling everything. But with .map(), things look better:

The Cool Way

let someNumz = [2, 3, 5, 4, 5];
let someTripledNumz = someNumz.map(item => item * 3);

console.log(someNumz); //-- [2, 3, 5, 4, 5]
console.log(someTripledNumz); //-- [6, 9, 15, 12, 15]

What if we want to map() over the values of an object?

As it stands, you can’t. There’s no .map() method that exists on any object for us to use out of the box. But, thanks to prototypal inheritance, JavaScript provides a way to make that happen. We’re gonna try it out, so that afterward, we’ll be able to do something like this:

let oldObject = {
  first: 1,
  second: 2,
  third: 3
};

let newObject = oldObject.map(function(item, index, thing) {
  return item * 2;
});

console.log(newObject);

// { first: 2, second: 4, third: 6 }

Let’s Map() Over an Object

First, let’s add an empty .map() method to the Object prototype. That method will accept a single argument: the callback method we’d like to fire on each of the values.

Object.prototype.map = function (func) {}

Next up, let’s pull all of our values out of the object we we’d like to map (it’ll be available within the this object), and actually map() over them like any other array, firing our function on each item.

Object.prototype.map = function (func) {
  // Collect an array of each value within the object.
  let oldValues = Object.values(this);

  // Manipulate those values with the provided callback method.
  let newValues = oldValues.map((item, index) => {
    return func.call(null, item, index, this);
  });
}

Notice that when we fire that method, we’re also passing in the index and the original object itself as parameters. This is to keep our method as close to the actual specification as reasonably possible.

Once we have those manipulated values, we can piece our object back together with the appropriate keys.

Object.prototype.map = function (func) {
  // Collect an array of each value within the object.
  let oldValues = Object.values(this);

  // Manipulate those values with the provided callback method.
  let newValues = oldValues.map((item, index) => {
    return func.call(null, item, index, this);
  });

  // Reconstruct our object with each modified value.
  let mappedObject = {};
  Object.keys(this).forEach(function (key, index) {
      mappedObject[key] = newValues[index];
  });

  return mappedObject;
}

We did it! But if you’re like me, you’re hungry for more. So, let’s:

Sidebar: It was really difficult to commit to using that meme. So overused. So 2013. But I’m choosing to submit to its applicability for this post. Now, we continue:

Let’s Map() Over a String()

This one’s a bit simpler than our Object map. Turn the target string into an array, map() over it as per usual, and turn it back into a string:

String.prototype.map = function(func) {
  let stringArray = this.split("");

  let newStringArray = stringArray.map((item, index) => {
    return func.call(window, item, index, this);
  });

  return newStringArray.join("");
};

Then, we can do something like this:

let newString = "I want to scream this!".map(function (item, index, thing) {
  return item.toUpperCase();
});

console.log(newString);

// 'I WANT TO SCREAM THIS!'

Let’s Map() Over a Set()

Since it’s array-like, mapping over a Set is also relatively straightforward. Make it into an array, do the things, turn it back into a set. Thanks to the syntax of modern JavaScript, it looks pretty clean.

Set.prototype.map = function(func) {
  let setArray = [...this];

  let newSetArray = setArray.map((item, index) => {
    return func.call(window, item, index, this);
  });

  return new Set(newSetArray);
};
let oldSet = new Set([1, 2, 3, 4, 5]);

let newSet = oldSet.map(function (item, index, thing) {
  return item * 3;
});

console.log(newSet);

// Set { 3, 6, 9, 12, 15 }

Let’s Map() Over a Map

This is where things get a wee bit nuttier. As we did before, the first thing we want to do in performing a map() on a Map() is get our target Map as an array. Take note of the format of this new array. Each item in that array is stored as an array itself, with the first item being the key, and the second being the value:

let myMap = new Map();
myMap.set('item 1', 'value 1');
myMap.set('item 2', 'value 2');

console.log([...myMap]);

// [ [ 'item 1', 'value 1' ], [ 'item 2', 'value 2' ] ]

So, our prototype method beings like this:

Map.prototype.map = function(func) {
  let mapAsArray = [...this];
};

And then, we’ll map over each item, handing if off to our callback method to do its thing.

Map.prototype.map = function(func) {
  let mapAsArray = [...this];

  let newMapAsArray = mapAsArray.map((item, index) => {
    return func.call(window, item, index, this);
  });
};

And finally, piece that sucker back together by creating a new Map and adding values it it from our newMapAsArray.

Map.prototype.map = function(func) {
  let mapAsArray = [...this];

  let newMapAsArray = mapAsArray.map((item, index) => {
    return func.call(window, item, index, this);
  });

  // Construct a new Map() with our modified values.
  let newMap = new Map();
  newMapAsArray.forEach(item => {
    // Remember, each item contains the [key, value] for our Map item.
    newMap.set(item[0], item[1]);
  });

  return newMap;
};

Let’s try it out. If you weren’t aware, the keys or values don’t have to be of a particular type — objects, numbers, functions, etc. So, map() with responsibility.

let myMap = new Map();
myMap.set('my first item', 1);
myMap.set({}, 2);

let newMap = myMap.map(function (item, index, thing) {
  // We only want to modify the _value_ of the item.
  item[1] = item[1] * 3;
  return item;
});

// Turn it back into an array just for ease of inspection.
console.log([...newMap]);

// [ [ 'my first item', 3 ], [ {}, 6 ] ]

Try the Package

Since what I wrote here includes a lot of functional, possibly useful code, I wrapped it all up into a package, ready for consumption. You can check it out here, and/or dive right in with npm install map-everything.

Code with Caution

Most of this was born out of “wouldn’t it be cool, if” within my brain. So, before shipping anything to production, be very intentional about what you’re manipulating, or deciding if it’s even worth adding these methods to all of these prototypes. All that said, this sure was fun! Thanks for mapping with me.


Alex MacArthur is a software engineer working for Dave Ramsey in Nashville-ish, TN.
Soli Deo gloria.

Get irregular emails about new posts or projects.

No spam. Unsubscribe whenever.
Leave a Free Comment

0 comments