简体   繁体   English

根据javascript中深度嵌套对象中的值过滤数组

[英]Filtering array based on value in deeply nested object in javascript

I have array with following structure:我有以下结构的数组:

var topics = [
  {
    "id": 1,
    "name": "topic title 1",
    "sub_categories": [
      {
        "id": 1,
        "name": "category title 1",
        "indicators": [
          {
            "id": 1,
            "name": "indicator 1",
            "sub_category_id": 1
          },
          {
            "id": 7,
            "name": "indicator 7 - foo",
            "sub_category_id": 1
          }
        ]
      },
      {
        "id": 6,
        "name": "category title 6",
        "indicators": [
          {
            "id": 8,
            "name": "indicator 8",
            "sub_category_id": 6
          }
        ]
      }
    ]
  },
  {
    "id": 2,
    "name": "topic title 2",
    "sub_categories": [
      {
        "id": 2,
        "name": "category 2",
        "indicators": [
          {
            "id": 2,
            "name": "indicator 2 - foo",
            "sub_category_id": 2
          }
        ]
      },
      {
        "id": 4,
        "name": "category 4",
        "indicators": [
          {
            "id": 5,
            "name": "indicator 5",
            "sub_category_id": 4
          }
        ]
      }
    ]
  }
];

I need to get filtered array based on value of name property in indicators array, removing non-matched indicators and both topic and sub_categories with empty indicators.我需要根据指标数组中名称属性的值获取过滤数组,删除不匹配的指标以及带有空指标的主题和子类别。 So for input of foo , result would be:所以对于foo输入,结果将是:

var topics = [
  {
    "id": 1,
    "name": "topic title 1",
    "sub_categories": [
      {
        "id": 1,
        "name": "category title 1",
        "indicators": [
          {
            "id": 7,
            "name": "indicator 7 - foo",
            "sub_category_id": 1
          }
        ]
      }
    ]
  },
  {
    "id": 2,
    "name": "topic title 2",
    "sub_categories": [
      {
        "id": 2,
        "name": "category 2",
        "indicators": [
          {
            "id": 2,
            "name": "indicator 2 - foo",
            "sub_category_id": 2
          }
        ]
      }
    ]
  }
];

I tried to use lodash methods based on other similar SO question but all examples either have only one level of nesting or same keys on all levels (ie. children).我尝试使用基于其他类似 SO 问题的 lodash 方法,但所有示例要么只有一层嵌套,要么在所有级别(即子项)上都有相同的键。 I would be fine with either getting back new array or mutating existing one.无论是取回新数组还是改变现有数组,我都可以。

Here is an ES6 solution based on reduce , filter and Object.assign :这是一个基于reducefilterObject.assign的 ES6 解决方案:

 function filterTree(topics, find) { return topics.reduce(function (acc, topic) { const sub_categories = topic.sub_categories.reduce(function (acc, cat) { const indicators = cat.indicators.filter( ind => ind.name.includes(find) ); return !indicators.length ? acc : acc.concat(Object.assign({}, cat, { indicators })); }, []); return !sub_categories.length ? acc : acc.concat(Object.assign({}, topic, { sub_categories })); }, []); } // sample data const topics = [ { "id": 1, "name": "topic title 1", "sub_categories": [ { "id": 1, "name": "category title 1", "indicators": [ { "id": 1, "name": "indicator 1", "sub_category_id": 1 }, { "id": 7, "name": "indicator 7 - foo", "sub_category_id": 1 } ] }, { "id": 6, "name": "category title 6", "indicators": [ { "id": 8, "name": "indicator 8", "sub_category_id": 6 } ] } ] }, { "id": 2, "name": "topic title 2", "sub_categories": [ { "id": 2, "name": "category 2", "indicators": [ { "id": 2, "name": "indicator 2 - foo", "sub_category_id": 2 } ] }, { "id": 4, "name": "category 4", "indicators": [ { "id": 5, "name": "indicator 5", "sub_category_id": 4 } ] } ] } ]; // Call the function var res = filterTree(topics, 'foo'); // Output result console.log(res);
 .as-console-wrapper { max-height: 100% !important; top: 0; }

You could use an iterative and recursive approach for filtering the given array, without hard wired properties.您可以使用迭代和递归方法来过滤给定的数组,而无需硬连线属性。

 const deepFilter = (array, indicator) => { return array.filter(function iter(o) { return Object.keys(o).some(k => { if (typeof o[k] === 'string' && o[k].includes(indicator)) { return true; } if (Array.isArray(o[k])) { o[k] = o[k].filter(iter); return o[k].length; } }); }); } const topics = [{ id: 1, name: "topic title 1", sub_categories: [{ id: 1, name: "category title 1", indicators: [{ id: 1, name: "indicator 1", sub_category_id: 1 }, { id: 7, name: "indicator 7 - foo", sub_category_id: 1 }] }, { id: 6, name: "category title 6", indicators: [{ id: 8, name: "indicator 8", sub_category_id: 6 }] }] }, { id: 2, name: "topic title 2", sub_categories: [{ id: 2, name: "category 2", indicators: [{ id: 2, name: "indicator 2 - foo", sub_category_id: 2 }] }, { id: 4, name: "category 4", indicators: [{ id: 5, name: "indicator 5", sub_category_id: 4 }] }] }]; console.log(deepFilter(topics, 'foo'));
 .as-console-wrapper { max-height: 100% !important; top: 0; }

This can pretty much all be done with ES 5 array methods (no library or polyfill needed for IE 9+):这几乎可以通过 ES 5 数组方法完成(IE 9+ 不需要库或 polyfill):

var passed = topics.filter(function(x) {
  return x.subcategories.some(function(y) {
    return y.indicators.some(function(z) {
      return Boolean(z.name.match(/foo/));
    });
  });
});

While this is total one-off code, the situation is perhaps too complicated for an easily digestible general-purpose solution (although I'd love to see someone prove me wrong).虽然这完全是一次性代码,但对于易于理解的通用解决方案来说,情况可能太复杂了(尽管我很想看到有人证明我错了)。

UPDATE更新

After taking a closer look at the output you will need to use reduce instead of filter:在仔细查看输出后,您将需要使用reduce而不是 filter:

var passed = topics.reduce((acc, x) => {
  var hasfoo = x.subcategories.reduce((accum, y) => {
    var ls = y.indicators.filter(z => z.name.match(/foo/));
    if (ls.length) {
      accum.push(Object.assign({}, y, {indicators: ls}));
    }
    return accum;
  }, []);

  if (hasfoo.length) {
    acc.push(Object.assign({}, x, {subcategories: hasfoo}));
  }

  return acc;
}, []);

Astute readers will note the recursive pattern here.精明的读者会注意到这里的递归模式。 Abstracting that out is left as an exercise, I'm tapped out.抽象出来作为练习,我被挖掘出来了。 Object.assign will need to be polyfilled for old browsers (trivial though). Object.assign需要为旧浏览器填充(虽然很简单)。

this will also modify existing topics这也将修改现有topics

var result = topics.filter(top => 
    (top.sub_categories = top.sub_categories.filter(cat => 
        (cat.indicators = cat.indicators.filter(i => i.name.match(/foo/))).length)
    ).length
);

Example例子

 var topics = [{ "id": 1, "name": "topic title 1", "sub_categories": [{ "id": 1, "name": "category title 1", "indicators": [{ "id": 1, "name": "indicator 1", "sub_category_id": 1 }, { "id": 7, "name": "indicator 7 - foo", "sub_category_id": 1 }] }, { "id": 6, "name": "category title 6", "indicators": [{ "id": 8, "name": "indicator 8", "sub_category_id": 6 }] }] }, { "id": 2, "name": "topic title 2", "sub_categories": [{ "id": 2, "name": "category 2", "indicators": [{ "id": 2, "name": "indicator 2 - foo", "sub_category_id": 2 }] }, { "id": 4, "name": "category 4", "indicators": [{ "id": 5, "name": "indicator 5", "sub_category_id": 4 }] }] }]; var result = topics.filter(top => (top.sub_categories = top.sub_categories.filter(cat => (cat.indicators = cat.indicators.filter(i => i.name.match(/foo/))).length)).length); console.log(result);

One more implementation.再一个实现。

        topics.forEach(function(topic, indexTopic, indexTopicArray) { 
                topic.sub_categories.forEach(function(subCat, indexsubCat, arraysubCat) { 
                         subCat.indicators = subCat.indicators.filter(indic => indic.name.includes("foo"));    
                         if(subCat.indicators.length === 0) { 
                                indexTopicArray[indexTopic].sub_categories.splice(indexsubCat, 1); 
    }})});
    console.log(topics);

Complete Code.完整代码。

 var topics = [ { "id": 1, "name": "topic title 1", "sub_categories": [ { "id": 1, "name": "category title 1", "indicators": [ { "id": 1, "name": "indicator 1", "sub_category_id": 1 }, { "id": 7, "name": "indicator 7 - foo", "sub_category_id": 1 } ] }, { "id": 6, "name": "category title 6", "indicators": [ { "id": 8, "name": "indicator 8", "sub_category_id": 6 } ] } ] }, { "id": 2, "name": "topic title 2", "sub_categories": [ { "id": 2, "name": "category 2", "indicators": [ { "id": 2, "name": "indicator 2 - foo", "sub_category_id": 2 } ] }, { "id": 4, "name": "category 4", "indicators": [ { "id": 5, "name": "indicator 5", "sub_category_id": 4 } ] } ] } ]; topics.forEach(function(topic, indexTopic, indexTopicArray) { topic.sub_categories.forEach(function(subCat, indexsubCat, arraysubCat) { subCat.indicators = subCat.indicators.filter(indic => indic.name.includes("foo")); if(subCat.indicators.length === 0) { indexTopicArray[indexTopic].sub_categories.splice(indexsubCat, 1); }})}); console.log(topics);

You can do it using _.filterDeep from deepdash extension for lodash :您可以使用lodashdeepdash扩展中的lodashlodash

var endsWith = 'foo';
var foundFoo = _.filterDeep(
  obj,
  function(value, key) {
    return _.endsWith(value.name, endsWith);
  },
  { tree: { children: ['sub_categories', 'indicators'] } }
);

Here is a full test for your case这是对您的案例的完整测试

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM