使用lodash递归收集属性的值

时间:2016-10-02 20:22:24

标签: javascript recursion lodash

对于嵌套的复杂对象或数组,我想收集给定属性名称的所有值。例如:

var structure = {
    name: 'alpha',
    array: [
        { name: 'beta' },
        { name: 'gamma' }
    ],
    object: {
        name: 'delta',
        array: [
            { name: 'epsilon' }
        ]
    }
};

// expected result:  [ 'alpha', 'beta', 'gamma', 'delta', 'epsilon' ]

如何使用普通的JS实现这一点显而易见,但是:使用lodash是否有任何优雅,简洁的方法?

[edit]下面的当前变体。欢迎使用更好的解决方案!

function getPropertyRecursive(obj, property) {
    var values = [];
    _.each(obj, function(value, key) {
        if (key === property) {
            values.push(value);
        } else if (_.isObject(value)) {
            values = values.concat(getPropertyRecursive(value, property));
        }
    });
    return values;
}

5 个答案:

答案 0 :(得分:6)

这可以通过以下mixin优雅地完成,这是_.toPairs的递归版本:

_.mixin({
    toPairsDeep: obj => _.flatMap(
        _.toPairs(obj), ([k, v]) =>
            _.isObjectLike(v) ? _.toPairsDeep(v) : [[k, v]])
});

然后得到你想要的结果:

result = _(structure)
    .toPairsDeep()
    .map(1)
    .value()

如果有name以外的标量属性,则必须将其过滤掉:

result = _(structure)
    .toPairsDeep()
    .filter(([k, v]) => k === 'name')
    .map(1)
    .value()

答案 1 :(得分:1)

我知道没有Lodash / Underscore功能可以满足您的需求。

那你想做什么?好吧,具体您希望从聚合结构中提取所有name属性的值。我们如何概括呢?换句话说,如果您希望将此类功能添加到Lodash / Underscore,您将如何重新构建问题?毕竟,大多数人不想获得name属性的值。您可以创建一个通用函数,在其中提供所需属性的名称,但是......比这更抽象地思考,您真正想要做的是访问聚合结构中的所有节点并使用它们执行某些操作。如果我们将JavaScript中的聚合结构视为通用树,我们可以使用深度优先的步骤采用递归方法:

function walk(o, f) {
    f(o);
    if(typeof o !== 'object') return;
    if(Array.isArray(o)) return o.forEach(e => walk(e, f));
    for(let prop in o) walk(o[prop], f);
}

现在,我们可以通过遍历结构并将数据添加到数组来完成您正在寻找的内容:

const arr = [];
walk(structure, x => if(x !== undefined && x.name) arr.push(x.name));

这对我的口味来说还不够实用,但是......这里对arr有副作用。因此,一个更好的通用方法(IMO)将允许上下文对象(或者如果你愿意,可以使用累加器,la Array#reduce):

function walk(o, f, context) {
    f(o, context);
    if(typeof o !== 'object') return context;
    if(Array.isArray(o)) return o.forEach(e => walk(e, f, context)), context;
    for(let prop in o) walk(o[prop], f, context);
    return context;
}

现在你可以这样称呼它,副作用免费:

const arr = walk(structure, (x, context) => {
  if(x !== undefined && x.name) context.push(x.name);
}, []);

答案 2 :(得分:1)

您可以迭代对象并再次为数组或对象调用它。然后得到想要的财产。



'use strict';

function getProperty(object, key) {

    function iter(a) {
        var item = this ? this[a] : a;
        if (this && a === key) {
            return result.push(item);
        }
        if (Array.isArray(item)) {
            return item.forEach(iter);
        }
        if (item !== null && typeof item === 'object') {
            return Object.keys(item).forEach(iter, item);
        }
    }

    var result = [];
    Object.keys(object).forEach(iter, object);
    return result;
}

var structure = { name: 'alpha', array: [{ name: 'beta' }, { name: 'gamma' }], object: { name: 'delta', array: [{ name: 'epsilon' }] } };

console.log(getProperty(structure,'name'));

.as-console-wrapper { max-height: 100% !important; top: 0; }




答案 3 :(得分:1)

使用 User id name auths //an array that will contain all instances of Auth by this User Auth: id token user //an object of User to whom this Auth object belongs to 递归迭代对象:

_.reduce()
function getPropertyRecursive(obj, prop) {
  return _.reduce(obj, function(result, value, key) {
    if (key === prop) {
      result.push(value);
    } else if (_.isObjectLike(value)) {
      return result.concat(getPropertyRecursive(value, prop));
    }

    return result;
  }, []);
}

var structure = {
  name: 'alpha',
  array: [{
    name: 'beta'
  }, {
    name: 'gamma'
  }],
  object: {
    name: 'delta',
    array: [{
      name: 'epsilon'
    }]
  }
};

var result = getPropertyRecursive(structure, 'name');
console.log(result);

答案 4 :(得分:0)

根据答案(https://stackoverflow.com/a/39822193/3443096),这是mixin的另一个想法:

_.mixin({
  extractLeaves: (obj, filter, subnode, subpathKey, rootPath, pathSeparator) => {
    var filterKv = _(filter).toPairs().flatMap().value()
    var arr = _.isArray(obj) ? obj : [obj]
    return _.flatMap(arr, (v, k) => {
      if (v[filterKv[0]] === filterKv[1]) {
        var vClone = _.clone(v)
        delete vClone[subnode]
        vClone._absolutePath = rootPath + pathSeparator + vClone[subpathKey]
        return vClone
      } else {
        var newRootPath = rootPath
        if (_.isArray(obj)) {
          newRootPath = rootPath + pathSeparator + v[subpathKey]
        }  
        return _.extractLeaves(
          v[subnode], filter, subnode, 
          subpathKey, newRootPath, pathSeparator
        )
      } 
    })
  }
});

此示例适用于此示例JSON,您要在其中提取叶节点:

{
    "name": "raka",
    "type": "dir",   
    "children": [{
        "name": "riki",
        "type": "dir",
        "children": [{
            "name": "roko",
            "type": "file"
        }]
    }]
}

以这种方式使用:

_.extractLeaves(result, {type: "file"}, "children", "name", "/myHome/raka", "/")

你会得到:

[
  {
    "name": "roko",
    "type": "file",
    "_absolutePath": "/myHome/raka/riki/roko"
  }
]