slotdp02
slotdp02

Reputation: 438

Filtering array of objects by searching nested object properties

I have an array of objects that I want to filter by comparing a nested property to a search term.

For example:

 var array = [
      {category: 'Business'
       users: [
                {name: 'Sally'
                 tags: [{tag: 'accounting'}, {tag: 'marketing'},...]
                },
                {name: 'Bob'
                 tags: [{tag: 'sales'}, {tag: 'accounting'},...]
                }...
              ]
       },
       {category: 'Heritage'
        users: [
                 {name: 'Linda'
                  tags: [{tag: 'Italy'}, {tag: 'Macedonia'},...]
                 },
                 {name: 'George'
                  tags: [{tag: 'South Africa'}, {tag: 'Chile'},...]
                 },...
               ]
       },...
    [

Essentially I want to filter the base array of objects by a search terms that include characters from the tag property string in the nested objects 2 arrays down.

So a search for 'market' would result in

[
  {category: 'Business'
   users: [
            {name: 'Sally'
             tags: [{tag: 'accounting'}, {tag: 'marketing'},...]
            },
            {name: 'Bob'
             tags: [{tag: 'sales'}, {tag: 'accounting'},...]
            }...
          ]
   }
]

Thank you.

Upvotes: 6

Views: 12823

Answers (6)

Nina Scholz
Nina Scholz

Reputation: 386624

You could use Array#filter with looking into the nested arrays by using Array#some.

If the tag is found in a nested array, then iteration stops and the result is given back to the filter callback.

var array = [{ category: 'Business', users: [{ name: 'Sally', tags: [{ tag: 'accounting' }, { tag: 'marketing' }] }, { name: 'Bob', tags: [{ tag: 'sales' }, { tag: 'accounting' }] }] }, { category: 'Heritage', users: [{ name: 'Linda', tags: [{ tag: 'Italy' }, { tag: 'Macedonia' }] }, { name: 'George', tags: [{ tag: 'South Africa' }, { tag: 'Chile' }] }] }],
    tag = 'marketing',
    result = array.filter(a => a.users.some(u => u.tags.some(t => t.tag.includes(tag))));

console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Upvotes: 10

Mihai Pantea
Mihai Pantea

Reputation: 360

The concatAll and concatMap definitions are taken from http://reactivex.io/learnrx/

Array.prototype.concatAll = function() {
    var results = [];
    this.forEach(function(subArray) {
        results.push.apply(results, subArray);
    });

    return results;
};

Array.prototype.concatMap = function(projectionFunctionThatReturnsArray) {
    return this.
        map(function(item) {
            return projectionFunctionThatReturnsArray(item);
        }).
        // apply the concatAll function to flatten the two-dimensional array
        concatAll();
};

function filterByTags(keyword) {
  return array.filter(function (item) {
    var allTags = item.users.concatMap(function (user) {
      return user.tags.map(function (tag) {
        return tag.tag;
      });
    });

    return allTags.some(function (tag) {
      return tag.indexOf(keyword) > -1;
    });
  });
}

console.log(filterByTags('market'));

Of course you could inline the allTags variable for more conciseness.

The filter applied to the initial array will return all items that have users whose tags contain the keyword supplied. The strategy is to build a flattened version of the users' tags and apply some on that.

Upvotes: 1

RomanPerekhrest
RomanPerekhrest

Reputation: 92854

The solution using Array.prototype.some() function:

var arr = [{ category: 'Business', users: [{ name: 'Sally', tags: [{ tag: 'accounting' }, { tag: 'marketing' }] }, { name: 'Bob', tags: [{ tag: 'sales' }, { tag: 'accounting' }] }] }, { category: 'Heritage', users: [{ name: 'Linda', tags: [{ tag: 'Italy' }, { tag: 'Macedonia' }] }, { name: 'George', tags: [{ tag: 'South Africa' }, { tag: 'Chile' }] }] }], 
    search_key = 'market',
    result = [];
    
arr.forEach(function(o){
    if (o.users.some(function(v){
        return v.tags.some(function(i){ return i.tag.indexOf(search_key) !== -1; });
    })) {
        result.push(o);
    }
});

console.log(result);

Upvotes: 2

Nisarg Shah
Nisarg Shah

Reputation: 14541

Note: I am taking a shortcut-like approach to this, primarily to provide a different perspective to the problem.

Instead of deep-searching the properties and arrays under the main array, you can create a json string of the users property and search within that. So I have created a new property usersString that temporarily stores the JSON string of the value against users property.

item.usersString = JSON.stringify(item.users);

Now, this would not be a perfect implementation, but it would almost always work. Also, if you stored this property within the browser (without storing it back to the DB), and used it to quick-search for every time user searches, I think it would be more performant that deep-searching entire array.

var array = [{
    category: 'Business',
    users: [{
        name: 'Sally',
        tags: [{
          tag: 'accounting'
        }, {
          tag: 'marketing'
        }]
      },
      {
        name: 'Bob',
        tags: [{
          tag: 'sales'
        }, {
          tag: 'accounting'
        }]
      }
    ]
  },
  {
    category: 'Heritage',
    users: [{
        name: 'Linda',
        tags: [{
          tag: 'Italy'
        }, {
          tag: 'Macedonia'
        }]
      },
      {
        name: 'George',
        tags: [{
          tag: 'South Africa'
        }, {
          tag: 'Chile'
        }]
      }
    ]
  }
];

var key = "market";

// Convert the users property into a string - so that it works as a quick search target.
array.forEach(function(item) {
  item.usersString = JSON.stringify(item.users);
});


var filteredItems = array.filter(function(item) {
  return item.usersString.toLowerCase().indexOf(key.toLowerCase()) >= 0;
});

// Delete the usersString property - if required.
filteredItems.forEach(function(item) {
  item.usersString = undefined;
  // Or,
  // delete item.usersString;
})

console.log(filteredItems);

Upvotes: 0

Ali Adlavaran
Ali Adlavaran

Reputation: 3735

Try this:

function search(term){
return
    Array.filter(array,function(item){
       return JSON.stringify(obj).indexOf(term)!=-1;
    });
}

So :

console.log(search('market'));

I hope to be helpful for you:)

Upvotes: 1

Koby Douek
Koby Douek

Reputation: 16675

You can use array.filter like this:

function getFiltered(val) {
     return array.filter(category == val);
}

This function will return a new array instance, only with the category keys you passed as the val params.

Upvotes: 0

Related Questions