Underscore.js has a very useful map
function.
_.map([1, 2, 3], function(num){ return num * 3; });
=> [3, 6, 9]
_.map({one: 1, two: 2, three: 3
Here's my version - slightly lengthy so I expect it can be shortened, but works with arrays and objects and no external dependencies:
function deepMap(obj, f, ctx) {
if (Array.isArray(obj)) {
return obj.map(function(val, key) {
return (typeof val === 'object') ? deepMap(val, f, ctx) : f.call(ctx, val, key);
});
} else if (typeof obj === 'object') {
var res = {};
for (var key in obj) {
var val = obj[key];
if (typeof val === 'object') {
res[key] = deepMap(val, f, ctx);
} else {
res[key] = f.call(ctx, val, key);
}
}
return res;
} else {
return obj;
}
}
demo at http://jsfiddle.net/alnitak/0u96o2np/
EDIT slightly shortened now by using ES5-standard Array.prototype.map
for the array case
Here's a Lodash solution using transform
function deepMap(obj, iterator, context) {
return _.transform(obj, function(result, val, key) {
result[key] = _.isObject(val) /*&& !_.isDate(val)*/ ?
deepMap(val, iterator, context) :
iterator.call(context, val, key, obj);
});
}
_.mixin({
deepMap: deepMap
});
If I understand correctly, here's an example, using recursion:
var deepMap = function(f, obj) {
return Object.keys(obj).reduce(function(acc, k) {
if ({}.toString.call(obj[k]) == '[object Object]') {
acc[k] = deepMap(f, obj[k])
} else {
acc[k] = f(obj[k], k)
}
return acc
},{})
}
Then you can use it like so:
var add1 = function(x){return x + 1}
var o = {
a: 1,
b: {
c: 2,
d: {
e: 3
}
}
}
deepMap(add1, o)
//^ { a: 2, b: { c: 3, d: { e: 4 } } }
Note that the mapping function has to be aware of the types, otherwise you'll get unexpected results. So you'd have to check the type in the mapping function if nested properties can have mixed types.
For arrays you could do:
var map1 = function(xs){return xs.map(add1)}
var o = {
a: [1,2],
b: {
c: [3,4],
d: {
e: [5,6]
}
}
}
deepMap(map1, o)
//^ { a: [2,3], b: { c: [4,5], d: { e: [6,7] } } }
Note that the callback is function(value, key)
so it works better with composition.
es5 underscore.js version, supports arrays (integer keys) and objects:
_.recursiveMap = function(value, fn) {
if (_.isArray(value)) {
return _.map(value, function(v) {
return _.recursiveMap(v, fn);
});
} else if (typeof value === 'object') {
return _.mapObject(value, function(v) {
return _.recursiveMap(v, fn);
});
} else {
return fn(value);
}
};
Here is a clean ES6 version:
function mapObject(obj, fn) {
return Object.keys(obj).reduce(
(res, key) => {
res[key] = fn(obj[key]);
return res;
},
{}
)
}
function deepMap(obj, fn) {
const deepMapper = val => typeof val === 'object' ? deepMap(val, fn) : fn(val);
if (Array.isArray(obj)) {
return obj.map(deepMapper);
}
if (typeof obj === 'object') {
return mapObject(obj, deepMapper);
}
return obj;
}
Here's the function I just worked out for myself. I'm sure there's a better way to do this.
// function
deepMap: function(data, map, key) {
if (_.isArray(data)) {
for (var i = 0; i < data.length; ++i) {
data[i] = this.deepMap(data[i], map, void 0);
}
} else if (_.isObject(data)) {
for (datum in data) {
if (data.hasOwnProperty(datum)) {
data[datum] = this.deepMap(data[datum], map, datum);
}
}
} else {
data = map(data, ((key) ? key : void 0));
}
return data;
},
// implementation
data = slf.deepMap(data, function(val, key){
return (val == 'undefined' || val == 'null' || val == undefined) ? void 0 : val;
});
I cheated on using underscore
.