【问题标题】:Filtering array based on value in deeply nested object in javascript基于javascript中深度嵌套对象中的值过滤数组
【发布时间】:2017-06-29 06:50:30
【问题描述】:

我有以下结构的数组:

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

我需要根据指标数组中名称属性的值获取过滤数组,删除不匹配的指标以及带有空指标的主题和子类别。因此对于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
          }
        ]
      }
    ]
  }
];

我尝试使用基于其他类似 SO 问题的 lodash 方法,但所有示例要么只有一层嵌套,要么在所有级别(即子级)上都有相同的键。我可以取回新数组或改变现有数组。

【问题讨论】:

  • 指标是仅在名称中还是在所有可能的属性中?
  • 如果您在名称值中引用“indicator”,仅用于示例目的,名称值可以是任何值。然而,层次结构总是相同的。

标签: javascript arrays json lodash


【解决方案1】:

您可以使用迭代和递归的方法来过滤给定的数组,而无需硬连接属性。

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

【讨论】:

  • 优雅的解决方案!谢谢
  • 你能解释一下这个函数是如何工作的吗?我很困惑。
  • 好的,我现在明白了一点。你如何使它适应 ES6?
  • 哈哈,最后,如果你过滤的是一个对象,而不是一个对象数组,你会如何调整它?
  • @NicScozzaro,这取决于对象。
【解决方案2】:

您可以使用 _.filterDeep from deepdash 扩展 lodash 来做到这一点:

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

这是您的情况的full test

【讨论】:

    【解决方案3】:

    另一个实现。

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

    完整的代码。

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

    【讨论】:

      【解决方案4】:

      这也会修改现有的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
      );
      

      例子

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

      【讨论】:

        【解决方案5】:

        这几乎可以通过 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/));
            });
          });
        });
        

        虽然这完全是一次性代码,但对于一个易于理解的通用解决方案来说,情况可能过于复杂(尽管我很乐意看到有人证明我错了)。

        更新

        仔细查看输出后,您需要使用reduce 而不是过滤器:

        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;
        }, []);
        

        精明的读者会注意到这里的递归模式。把它抽象出来作为练习,我已经被淘汰了。 Object.assign 将需要为旧浏览器填充(虽然微不足道)。

        【讨论】:

        • 感谢您的快速响应,但您的解决方案会返回包含匹配指标的主题下的所有内容,因此我仍会返回包含不匹配指标列表的子类别。
        • @TeoDragovic 更新了答案。另请参阅 trinicot 的,与我更新的几乎完全相同。
        【解决方案6】:

        这是一个基于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; }

        【讨论】:

        • 有趣的是,在操作员发表评论后,我想出了一个几乎相同的解决方案。很好地使用对象速记属性。
        猜你喜欢
        • 2019-08-10
        • 1970-01-01
        • 1970-01-01
        • 2019-01-29
        • 2016-11-17
        • 2020-09-24
        • 2022-01-20
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多