JS - 深层地图功能

时间:2014-08-15 20:43:17

标签: javascript json dictionary

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]
}

8 个答案:

答案 0 :(得分:17)

这是使用transform

的Lodash解决方案
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
});

答案 1 :(得分:7)

这是我的版本 - 稍微冗长所以我希望它可以缩短,但是可以使用数组和对象而不需要外部依赖:

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;
    }
}

演示http://jsfiddle.net/alnitak/0u96o2np/

编辑现在使用ES5标准版Array.prototype.map稍微缩短了数组大小写

答案 2 :(得分:5)

这是一个干净的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;
}

答案 3 :(得分:3)

我已经发布了一个名为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

答案 4 :(得分:2)

如果我理解正确,这是一个例子,使用递归:

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 :(得分:0)

这是我刚刚为自己制定的功能。我确信有更好的方法可以做到这一点。

// 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 :(得分:0)

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);
    }
};

答案 7 :(得分:0)

基于@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;
});

此版本允许您替换偶数对象&amp;数组本身,并指定是否要使用iterateeExplore参数探索遇到的每个对象/数组。

请参阅this fiddle了解演示