admin管理员组

文章数量:1396763

I need to filter a nested array by matching a keyword. It must return all the objects that matches the name attribute of the parent OR one of its children and filter its children too.

Example:

[
  {
    "name": "abc",
    "children": [
      { "name": "abcd" },
      { "name": "efg" }
    ]
  },
  {
    "name": "hjk",
    "children": [
      { "name": "lmn" },
      { "name": "opq" }
    ]
  },
  {
    "name": "xyz",
    "children": [
      { "name": "lmn" },
      { "name": "abcdef" }
    ]
  }
]

If text input is "ab", then it must return:

[
  {
    "name": "abc",
    "children": [
      { "name": "abcd" }
    ]
  },
  {
    "name": "xyz",
    "children": [
      { "name": "abcdef" }
    ]
  }
]

(It matches the parent OR at least one of the children, and returns but parent and children)

Right now, I can only filter the parents like this:

filteredArray = _.filter(array, (parent) => {
    return _.includes(_.toLower(parent.name), _.toLower(filterText));
});

How can I modify this so it filters the children too?

EDIT: A parent can have empty or no children array.

I need to filter a nested array by matching a keyword. It must return all the objects that matches the name attribute of the parent OR one of its children and filter its children too.

Example:

[
  {
    "name": "abc",
    "children": [
      { "name": "abcd" },
      { "name": "efg" }
    ]
  },
  {
    "name": "hjk",
    "children": [
      { "name": "lmn" },
      { "name": "opq" }
    ]
  },
  {
    "name": "xyz",
    "children": [
      { "name": "lmn" },
      { "name": "abcdef" }
    ]
  }
]

If text input is "ab", then it must return:

[
  {
    "name": "abc",
    "children": [
      { "name": "abcd" }
    ]
  },
  {
    "name": "xyz",
    "children": [
      { "name": "abcdef" }
    ]
  }
]

(It matches the parent OR at least one of the children, and returns but parent and children)

Right now, I can only filter the parents like this:

filteredArray = _.filter(array, (parent) => {
    return _.includes(_.toLower(parent.name), _.toLower(filterText));
});

How can I modify this so it filters the children too?

EDIT: A parent can have empty or no children array.

Share Improve this question edited Jul 25, 2018 at 8:20 David Prieto asked Jul 25, 2018 at 7:34 David PrietoDavid Prieto 2,2996 gold badges34 silver badges51 bronze badges 5
  • please explain why "name": "xyz", "children": [ { "name": "abcdef" } ] is in filtered array? – Niladri Basu Commented Jul 25, 2018 at 7:36
  • Because the child with name "abcdef" marches the input "ab", so it returns both the matching child and the parent. Tha's what I'm trying to do. – David Prieto Commented Jul 25, 2018 at 7:40
  • can children have nested childrens? – Niladri Basu Commented Jul 25, 2018 at 7:41
  • No, only the nested structure you can see on the example. – David Prieto Commented Jul 25, 2018 at 7:43
  • please take a look at my solution, it uses plain JS and uses regexp as it suits here the best, also it is more readable IMO. – amankkg Commented Jul 25, 2018 at 9:21
Add a ment  | 

4 Answers 4

Reset to default 4

For every element check name and filter its children and if one of them is true, push element with filtered children to a new array

const data = [{"name": "abc", "children": [{ "name": "abcd" }, { "name": "efg" }  ] }, {"name": "hjk","children": [{ "name": "lmn"},  { "name": "opq" }  ]}, { "name": "xyz","children": [{ "name": "lmn"
},{"name": "abcdef" } ] }, {"name": "abc"}, {"name": "abc", children: []}];

const res = data.reduce((acc, a) => {
  const ch = a.children && a.children.filter(b => b.name.includes('ab'));
  if(ch && ch.length) acc.push({...a, children: ch});
  else if(a.name.includes('ab')) acc.push({ name: a.name });
  return acc;
}, []);

console.log(res);

There is no real need in lodash. #nodash! Just modern vanilla JS:

const regExp = new RegExp(filterText, 'i');

const result = array.reduce((acc, { name, children = [] }) => {
  const next = children.filter(child => child.name.match(regExp));

  if (name.match(regExp) || next.length > 0) {
    acc.push({ name, children: next });
  }

  return acc;
}, []);

name.match(regExp) will return null if substring is not found.

children = [] handles the case when parent has no children.

Also, note that checking for sub-string using RegExp should be much faster than performing multiple transformations and lookups (e.g. toLower, includes).

Here is the working repl with your example.

var arrayList = [
  {
    "name": "abc",
    "children": [
      { "name": "abcd" },
      { "name": "efg" }
    ]
  },
  {
    "name": "ab",
    "children": [
      { "name": "lmn" },
      { "name": "opq" }
    ]
  },
  {
    "name": "jdfj",
    "children": [
      { "name": "lmn" },
      { "name": "abcdef" }
    ]
  }
];

function filterArray(arrayList, search){
    return arrayList.filter((item) => {
        let childrens = item.children;
        if(childrens && childrens.length){
          item.children = filterArray(childrens, search);
          if(item.children && item.children.length){
              return true;
          }
        }
        return item.name.indexOf(search) > -1;
    });
}

const filter = filterArray(arrayList, 'ab');

console.log(filter);

IMHO, you can use Array#reduce() method to do something like this perhaps:

arr = [{"name":"abc","children":[{"name":"abcd"},{"name":"efg"}]},{"name":"hjk","children":[{"name":"lmn"},{"name":"opq"}]},{"name":"xyz","children":[{"name":"lmn"},{"name":"abcdef"}]}, {"name": "xyz"}]

var input = "ab"
filteredArr = arr.reduce((accum, ele) => {
  var obj = {};
  ele['children'] && ele['children'].forEach(e => {
    if (e['name'].includes(ele['name']) || e['name'].includes(input)) {
      obj['name'] = ele['name'];
      obj['children'] ? (obj['children'].push(e)) : (obj['children'] = [], obj['children'].push(e))
    }
  })
  if (Object.keys(obj).length > 0) accum.push(obj);
  return accum;
}, [])

console.log(filteredArr);

本文标签: javascriptFilter nested array by matching keywords in both parent or childStack Overflow