JS - функция глубокой карты
Underscore.js имеет очень полезную функцию map
.
_.map([1, 2, 3], function(num){ return num * 3; });
=> [3, 6, 9]
_.map({one: 1, two: 2, three: 3}, function(num, key){ return num * 3; });
=> [3, 6, 9]
Я ищу аналогичную функцию, которая может выполнять итерацию через вложенные объекты или глубокое сопоставление. После тонны поиска я не могу найти это. То, что я могу найти, - это нечто, чтобы вырвать глубокий объект, но не перебирать каждое значение глубокого объекта.
Что-то вроде этого:
deepMap({
one: 1,
two: [
{ foo: 'bar' },
{ foos: ['b', 'a', 'r', 's'] },
],
three: [1, 2, 3]
}, function(val, key) {
return (String(val).indexOf('b') > -1) ? 'bobcat' : val;
})
Как это сделать?
Пример вывода
{
one: 1,
two: [
{ foo: 'bobcat' },
{ foos: ['bobcat', 'a', 'r', 's'] },
],
three: [1, 2, 3]
}
Ответы
Ответ 1
Здесь решение Lodash с использованием 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
});
Ответ 2
Здесь моя версия - немного длинная, поэтому я ожидаю, что она может быть сокращена, но работает с массивами и объектами и без внешних зависимостей:
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/
РЕДАКТИРОВАТЬ теперь немного сокращен, используя ES5-standard Array.prototype.map
для случая массива
Ответ 3
Вот чистая версия ES6:
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;
}
Ответ 4
Если я правильно понимаю, вот пример, используя рекурсию:
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
},{})
}
Затем вы можете использовать его так:
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 } } }
Обратите внимание, что функция сопоставления должна знать типы, иначе вы получите неожиданные результаты. Поэтому вам нужно будет проверить тип в функции сопоставления, если вложенные свойства могут иметь смешанные типы.
Для массивов, которые вы могли бы сделать:
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] } } }
Обратите внимание, что обратный вызов function(value, key)
, поэтому он лучше работает с композицией.
Ответ 5
Здесь функция, которую я только что разработал для себя. Я уверен, что есть лучший способ сделать это.
// 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;
});
Я обманул использование underscore
.
Ответ 6
Я опубликовал пакет под названием Deep Map, чтобы решить эту проблему. И если вы хотите сопоставить объектные ключи, а не их значения, я написал Deep Map Keys.
Примечательно, что ни один из ответов здесь не затрагивает значительную проблему: круговые ссылки. Вот несколько наивная реализация, которая касается этих ротаторов:
function deepMap(value, mapFn, thisArg, key, cache=new Map()) {
// Use cached value, if present:
if (cache.has(value)) {
return cache.get(value);
}
// If value is an array:
if (Array.isArray(value)) {
let result = [];
cache.set(value, result); // Cache to avoid circular references
for (let i = 0; i < value.length; i++) {
result.push(deepMap(value[i], mapFn, thisArg, i, cache));
}
return result;
// If value is a non-array object:
} else if (value != null && /object|function/.test(typeof value)) {
let result = {};
cache.set(value, result); // Cache to avoid circular references
for (let key of Object.keys(value)) {
result[key] = deepMap(value[key], mapFn, thisArg, key, cache);
}
return result;
// If value is a primitive:
} else {
return mapFn.call(thisArg, value, key);
}
}
И вы можете использовать его следующим образом:
class Circlular {
constructor() {
this.one = 'one';
this.arr = ['two', 'three'];
this.self = this;
}
}
let mapped = deepMap(new Circlular(), str => str.toUpperCase());
console.log(mapped.self.self.self.arr[1]); // 'THREE'
Конечно, пример выше приведен в ES2015. См. Deep Map для более оптимизированной, хотя и менее простой, совместимой с ES5 реализации, написанной в TypeScript.
Ответ 7
es5 версия underscore.js, поддерживает массивы (целые ключи) и объекты:
_.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);
}
};
Ответ 8
Основываясь на ответе @megawac, я сделал некоторые улучшения.
function mapExploreDeep(object, iterateeReplace, iterateeExplore = () => true) {
return _.transform(object, (acc, value, key) => {
const replaced = iterateeReplace(value, key, object);
const explore = iterateeExplore(value, key, object);
if (explore !== false && replaced !== null && typeof replaced === 'object') {
acc[key] = mapExploreDeep(replaced, iterateeReplace, iterateeExplore);
} else {
acc[key] = replaced;
}
return acc;
});
}
_.mixin({
mapExploreDeep: mapExploreDeep;
});
Эта версия позволяет вам заменить даже объекты и массив самостоятельно и указать, хотите ли вы исследовать все объекты/массивы, встречающиеся с помощью параметра iterateeExplore
.
Смотрите эту скрипту для демонстрации