Search code examples
javascriptarraysdata-structuresecmascript-6javascript-objects

How can I filter my data based on multiple conditions?


Okay, so I have an array of objects which is the data I want to filter:

const posts = [
{
 hairday: '2',
 products: ['product1', 'product2', 'product3', 'product4'],
 rating: '2',
 drying: 'diffuser'
},
{
 hairday: '2',
 products: ['product6', 'product7', 'product8', 'product9'],
 rating: '4',
 drying: 'air dry'
},
{
 hairday: '3',
 products: ['product10', 'product11', 'product13', 'product14'],
 rating: '3',
 drying: 'air dry'
},
{
 hairday: '4',
 products: ['product15', 'product26', 'product37', 'product14'],
 rating: '5',
 drying: 'towel dry'
},
]

I would like to filter the above data by multiple conditions. Here is an example object of conditions:

{
 products: ['product1', 'product13'],
 hairday: ['2', '3'],
 drying: ['air dry', 'diffuser'],
 rating: []
}

So I want to get all of the post objects that match at least one of the items in each array.

So, the filtered items should have product1 OR product13 AND hairday 2 OR hairday 3 AND drying airdry OR drying diffuser AND any rating.

What's the best way to go through this? Is my filter object structured in the best way? Thanks in advance <3


Solution

  • For a more robust solution that doesn't require listing the keys manually, you can use Object.keys() to iterate through your criteria object, and then compare individual objects in the post array against it:

    const filtered = posts.filter(post => {
      const conditionKeys = Object.keys(conditions);
      const fulfillments = conditionKeys.map(k => {
        const condition = conditions[k];
        
        // If we encounter an empty array, then criteria is always met
        if (condition.length === 0) {
          return true;
        }
        
        // Enforces that as long as ONE subcondition is met (`OR`)
        return condition.filter(v => post[k].includes(v)).length > 0;
      });
      
      // Enforce that EVERY condition must be met (`AND`)
      // A condition is considered met as long as it is true
      return fulfillments.every(x => x);
    });
    

    When iterating through individual condition (hairday, products, etc.), we simply want to check if your object includes one or more of the listed values (i.e. of the two arrays intersect in anyway). If there is an intersection, the length will be >0, otherwise it will be 0.

    See proof-of-concept below:

    const posts = [{
        hairday: '2',
        products: ['product1', 'product2', 'product3', 'product4'],
        rating: '2',
        drying: 'diffuser'
      },
      {
        hairday: '2',
        products: ['product6', 'product7', 'product8', 'product9'],
        rating: '4',
        drying: 'air dry'
      },
      {
        hairday: '3',
        products: ['product10', 'product11', 'product13', 'product14'],
        rating: '3',
        drying: 'air dry'
      },
      {
        hairday: '4',
        products: ['product15', 'product26', 'product37', 'product14'],
        rating: '5',
        drying: 'towel dry'
      },
    ];
    
    const conditions = {
      products: ['product1', 'product13'],
      hairday: ['2', '3'],
      drying: ['air dry', 'diffuser'],
      rating: []
    };
    
    const filtered = posts.filter(post => {
      const fulfillments = Object.keys(conditions).map(k => {
        const condition = conditions[k];
        
        // If we encounter an empty array, then criteria is always met
        if (condition.length === 0) {
          return true;
        }
        
        return condition.filter(v => post[k].includes(v)).length > 0;
      });
      
      return fulfillments.every(x => x);
    });
    
    console.log(filtered);