7
\$\begingroup\$

I'm implementing a search for a deep array. It applies the search/filter to the deepest level of children below, i.e. { name: 'foo' }, { name: 'bar' }.

If all children are filtered from the parent object, the parent is removed from the list too.

Since I want to leave the original object intact, I copy each container object and add a new children property to it.

I think this has a lot of room for improvement. It feels like it can be simplified. Also, this doesn't feel DRY.

var data = [
    { 
    name: 'a1', children: [
        { 
        name: 'b1', children: [
            { name: 'foo' }, { name: 'bar' }
        ] 
      }
    ] 
  },
  { 
    name: 'a2', children: [
        { 
        name: 'b2', children: [
            { name: 'baz' }, { name: 'bah' }
        ] 
      }
    ] 
  }
];

function filterData(data, filter){
    var result = [];
    _.forEach(data, function(firstLevelItem){
    var newFirstLevelItem = _.extend({}, firstLevelItem, { children: [] });

    _.forEach(firstLevelItem.children, function(secondLevelItem){
        var newSecondLevelItem = _.extend({}, secondLevelItem, {
        children: _.filter(secondLevelItem.children, filter)
      });

      if(newSecondLevelItem.children.length > 0) {
        newFirstLevelItem.children.push(newSecondLevelItem);
      }
    });
    if(newFirstLevelItem.children.length > 0) {
        result.push(newFirstLevelItem);
    }
  });
  return result;
}

var result = filterData(data, function(item){
    return item.name === 'foo';
});

console.log(result);

https://jsfiddle.net/b2xsna5k/

\$\endgroup\$

1 Answer 1

7
\$\begingroup\$

I went functional on this one (as usual).

First of all, i removed the dependency from the third party library. I haven't anything in particular against that, but if the problem can be resolved easily with the standard tools, why bloat your app?

Then, since the main function deals mostly with lists, I looked for a way that would let me transform the list, filtering and transforming freely: reduce is a natural fit.

Now i can check the immediate list against a predicate (the filtering method you pass in) and recursively do the same on the list of childrens. The result of this filtering determines the final output structure.

Here's an implementation:

var data = [
    {
    name: 'a1', children: [
        {
        name: 'b1', children: [
            { name: 'gap' }, { name: 'bar' }
        ]
      }
    ]
  },
  {
    name: 'a2', children: [
        {
        name: 'b2', children: [
            { name: 'foo' }, { name: 'gap' }
        ]
      }
    ]
  }
];

function filterData(data, predicate) {

	// if no data is sent in, return null, otherwise transform the data
	return !!!data ? null : data.reduce((list, entry) => {

		let clone = null;
		if (predicate(entry)) {
			// if the object matches the filter, clone it as it is
			clone = Object.assign({}, entry)
		} else if (entry.children != null) {
			// if the object has childrens, filter the list of children
			let children = filterData(entry.children, predicate)
			if (children.length > 0) {
				// if any of the children matches, clone the parent object, overwrite
				// the children list with the filtered list
				clone = Object.assign({}, entry, {children: children})
			}
		}

		// if there's a cloned object, push it to the output list
		clone && list.push(clone)
		return list
	}, [])

}

var result = filterData(data, function(item) {
    return item.name === 'foo';
});

console.log(JSON.stringify(result, null, 2));

edit: I added some comments to the code. Shouldn't be needed, but it clarifies the steps.

\$\endgroup\$
1
  • \$\begingroup\$ Hi man. Just made some stuff base on your code but I've made one fix: not } else if (entry.children != null) { but if (entry.children != null) {. I've made separate condition and it works now \$\endgroup\$ Oct 30, 2018 at 12:43

This site is temporarily in read-only mode and not accepting new answers.

Not the answer you're looking for? Browse other questions tagged .