153

The map function in underscore.js, if called with a javascript object, returns an array of values mapped from the object's values.

_.map({one: 1, two: 2, three: 3}, function(num, key){ return num * 3; });
=> [3, 6, 9]

is there a way to make it preserve the keys? ie, I want a function that returns

{one: 3, two: 6, three: 9}
GG.
  • 21,083
  • 14
  • 84
  • 130
xuanji
  • 5,007
  • 2
  • 26
  • 35
  • If you, like me, came here looking for a 'mapValues' like function that changes the actual object instead of returning a new one, check this simple solution out: http://stackoverflow.com/questions/30894044/destructively-map-object-properties-function – Michahell Jun 17 '15 at 14:14

12 Answers12

255

With Underscore

Underscore provides a function _.mapObject to map the values and preserve the keys.

_.mapObject({ one: 1, two: 2, three: 3 }, function (v) { return v * 3; });

// => { one: 3, two: 6, three: 9 }

DEMO


With Lodash

Lodash provides a function _.mapValues to map the values and preserve the keys.

_.mapValues({ one: 1, two: 2, three: 3 }, function (v) { return v * 3; });

// => { one: 3, two: 6, three: 9 }

DEMO

GG.
  • 21,083
  • 14
  • 84
  • 130
  • There have been efforts to get a _.mapValues function into underscore: [Relevant Issue](https://github.com/jashkenas/underscore/issues/220), [Pull Request for _.mapValues](https://github.com/jashkenas/underscore/pull/1953). I hope this goes through :) – raffomania Dec 18 '14 at 09:54
  • it looks to me like you're making an OBJECT into an object, or am i out of my mind? – jsh Sep 28 '15 at 18:22
  • @jsh In this case, `_.map()` is returning `[['one', 3], ['two', 6], ['three', 9]]`, which is an array of arrays, and `_.object()` is turning it back into an object. – Jezen Thomas Dec 15 '15 at 10:17
59

I managed to find the required function in lodash, a utility library similar to underscore.

http://lodash.com/docs#mapValues

_.mapValues(object, [callback=identity], [thisArg])

Creates an object with the same keys as object and values generated by running each own enumerable property of object through the callback. The callback is bound to thisArg and invoked with three arguments; (value, key, object).

T J
  • 42,762
  • 13
  • 83
  • 138
xuanji
  • 5,007
  • 2
  • 26
  • 35
20

How about this version in plain JS (ES6 / ES2015)?

let newObj = Object.assign(...Object.keys(obj).map(k => ({[k]: obj[k] * 3})));

jsbin

If you want to map over an object recursively (map nested obj), it can be done like this:

const mapObjRecursive = (obj) => {
  Object.keys(obj).forEach(key => {
    if (typeof obj[key] === 'object') obj[key] = mapObjRecursive(obj[key]);
    else obj[key] = obj[key] * 3;
  });
  return obj;
};

jsbin

Since ES7 / ES2016 you can use Object.entries instead of Object.keys like this:

let newObj = Object.assign(...Object.entries(obj).map(([k, v]) => ({[k]: v * 3})));

And since ES2019 you can use Object.fromEntries.

let newObj = Object.fromEntries(Object.entries(obj).map(([k, v]) => ([k, v * 3])))
Mariusz Pawelski
  • 25,983
  • 11
  • 67
  • 80
Rotareti
  • 49,483
  • 23
  • 112
  • 108
19

var mapped = _.reduce({ one: 1, two: 2, three: 3 }, function(obj, val, key) {
    obj[key] = val*3;
    return obj;
}, {});

console.log(mapped);
<script src="http://underscorejs.org/underscore-min.js"></script>
<script src="https://getfirebug.com/firebug-lite-debug.js"></script>
kunalgolani
  • 437
  • 4
  • 7
13

I know this is old, but now Underscore has a new map for objects :

_.mapObject(object, iteratee, [context]) 

You can of course build a flexible map for both arrays and objects

_.fmap = function(arrayOrObject, fn, context){
    if(this.isArray(arrayOrObject))
      return _.map(arrayOrObject, fn, context);
    else
      return _.mapObject(arrayOrObject, fn, context);
}
Rayjax
  • 7,494
  • 11
  • 56
  • 82
  • 5
    Note to lodash users: jdalton has decided to [break compatibility with underscore.js](https://github.com/lodash/lodash/issues/980) over this change. lodash will not support `mapObject`; look at lodash's `mapValues` method instead. – Mark Amery Apr 28 '15 at 15:21
4

I know it's been a long time, but still the most obvious solution via fold (aka reduce in js) is missing, for the sake of completeness i'll leave it here:

function mapO(f, o) {
  return Object.keys(o).reduce((acc, key) => {
    acc[key] = f(o[key])
    return acc
  }, {})
}
Darwin
  • 183
  • 3
  • 4
  • I am okay with the use of Lodash library but programmers are using these kind of libraries, and not knowing how this things could be achieved in vanilla JS. So, I appreciate your answer! – cyonder Mar 31 '18 at 23:50
3

_.map returns an Array, not an Object.

If you want an object you're better off using a different function, like each; if you really want to use map you could do something like this:

Object.keys(object).map(function(value, index) {
   object[value] *= 3;
})

but that is confusing, when seeing map one would expect to have an array as result and then make something with it.

Alberto Zaccagni
  • 30,779
  • 11
  • 72
  • 106
  • I had a feeling reading the docs that it wouldn't be natural to try this in underscore.js. I think my use case is quite natural, why don't they support it? – xuanji Sep 26 '13 at 08:45
  • One reason could likely be that `map` is used to change an input producing an array as output, you could compose `_.object` and `_.map` as @GG. wrote, but that's a matter of taste at this point. – Alberto Zaccagni Sep 26 '13 at 08:48
3

I think you want a mapValues function (to map a function over the values of an object), which is easy enough to implement yourself:

mapValues = function(obj, f) {
  var k, result, v;
  result = {};
  for (k in obj) {
    v = obj[k];
    result[k] = f(v);
  }
  return result;
};
joyrexus
  • 390
  • 1
  • 3
  • 8
2
const mapObject = (obj = {}, mapper) =>
  Object.entries(obj).reduce(
    (acc, [key, val]) => ({ ...acc, [key]: mapper(val) }),
    {},
  );
Nigel Kirby
  • 408
  • 4
  • 5
1

_.map using lodash like loop to achieve this

 var result={};
_.map({one: 1, two: 2, three: 3}, function(num, key){ result[key]=num * 3; });
console.log(result)

//output
{one: 1, two: 2, three: 3}

Reduce is clever looks like above answare

_.reduce({one: 1, two: 2, three: 3}, function(result, num, key) {
  result[key]=num * 3
  return result;
}, {});

//output
{one: 1, two: 2, three: 3}
Balaji
  • 9,657
  • 5
  • 47
  • 47
0

A mix fix for the underscore map bug :P

_.mixin({ 
    mapobj : function( obj, iteratee, context ) {
        if (obj == null) return [];
        iteratee = _.iteratee(iteratee, context);
        var keys = obj.length !== +obj.length && _.keys(obj),
            length = (keys || obj).length,
            results = {},
            currentKey;
        for (var index = 0; index < length; index++) {
          currentKey = keys ? keys[index] : index;
          results[currentKey] = iteratee(obj[currentKey], currentKey, obj);
        }
        if ( _.isObject( obj ) ) {
            return _.object( results ) ;
        } 
        return results;
    }
}); 

A simple workaround that keeps the right key and return as object It is still used the same way as i guest you could used this function to override the bugy _.map function

or simply as me used it as a mixin

_.mapobj ( options , function( val, key, list ) 
Pascal
  • 2,377
  • 3
  • 25
  • 40
0

You can use _.mapValues(users, function(o) { return o.age; }); in Lodash and _.mapObject({ one: 1, two: 2, three: 3 }, function (v) { return v * 3; }); in Underscore.

Check out the cross-documentation here: http://jonathanpchen.com/underdash-api/#mapvalues-object-iteratee-identity

luxon
  • 417
  • 5
  • 14