使用lodash递归收集属性值

3

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

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

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

显然可以使用纯JS来实现,但是:是否有一种优雅、简洁的方法使用lodash?

[编辑] 下面是当前变体。欢迎更好的解决方案!

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个回答

11

以下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()

这里有很多非常好和有价值的答案。我接受了这个,因为它最接近我的问题中所提到的“lodash”。谢谢大家! - qqilihq

3

据我所知,没有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产生副作用。所以,在我的看法中,更好的通用方法是允许一个上下文对象(或者累加器,就像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

使用_.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);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.16.2/lodash.min.js"></script>


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


0

根据这个答案(https://dev59.com/qPH0s4cB2Jgan1znmaPy#39822193),这里有另一个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"
  }
]

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接