最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

Filtering array based on value in deeply nested object in javascript - Stack Overflow

programmeradmin7浏览0评论

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:

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). I would be fine with either getting back new array or mutating existing one.

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:

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). I would be fine with either getting back new array or mutating existing one.

Share Improve this question asked Feb 10, 2017 at 19:49 Teo DragovicTeo Dragovic 3,51821 silver badges36 bronze badges 2
  • is the indicator only in name or in all properties possible? – Nina Scholz Commented Feb 10, 2017 at 19:55
  • If you are reffering to "indicator" in name value that is only for the example purpose, name value can be anything. Hierarchy however, is always the same. – Teo Dragovic Commented Feb 10, 2017 at 20:00
Add a ment  | 

6 Answers 6

Reset to default 7

Here is an ES6 solution based on reduce, filter and Object.assign:

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+):

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 plicated 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:

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

this will also modify existing 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:

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

发布评论

评论列表(0)

  1. 暂无评论