Home > front end >  Filtering an array by multiple property values
Filtering an array by multiple property values

Time:12-13

I have an array of the form

var cars = [
   {name: "BMW X5", topsales: ["USA", "China", "Russia"], maxspeed: 250, users: ["teenage", "ladies", "mens"]}
   {name: "Volkswagen Touareg", topsales: ["USA", "Germany"], maxspeed: 240, users: ["teenage", "mens", "old mens"]}
   etc....
]

I am trying to filter, let's say like this:

var query = {
   topsales: ["USA", "China"],
   users: "teenage"
}
function nestedFilter(targetArray, filters) {
     var filterKeys = Object.keys(filters);
     return targetArray.filter(function (eachObj) {
         return filterKeys.every(function (eachKey) {
             if (!filters[eachKey].length) {
                 return true; 
             }
             return filters[eachKey].includes(eachObj[eachKey]);
          });
     });
};
goodresult = nestedFilter(cars, query);

But the function doesn't work as it should. If the object has one value in the property, then it filters, but if there are several of them, and I need at least one of them to satisfy the search, then it does not filter. Help who can please

CodePudding user response:

You could check if query is an array and/or the value is an array and check accordingly.

function nestedFilter(data, query) {
    const
        filters = Object.entries(query);

    return data.filter(o => filters.every(([k, v]) => Array.isArray(v)
        ? Array.isArray(o[k])
            ? v.some(s => o[k].includes(s))
            : v.includes(o[k])
        : Array.isArray(o[k])
            ? o[k].includes(v)
            : o[k] === v
    ));
}

const
    cars = [{ name: "BMW X5", topsales: ["USA", "China", "Russia"], maxspeed: 250, users: ["teenage", "ladies", "mens"] }, { name: "Volkswagen Touareg", topsales: ["USA", "Germany"], maxspeed: 240, users: ["teenage", "mens", "old mens"] }],
    query = { topsales: ["USA", "China"], users: "teenage" };

console.log(nestedFilter(cars, query));

CodePudding user response:

I am assuming that you intend to implement an OR functionality because you said at least one of them. Therefore the working code is below.

But before going on reading, please beware of below remarks:

  • I used some instead of every, because some works as or and every works as and. It means that that line will return true if the current car item matches at least one of the filters.

  • You should use item.includes(filter) instead of filter.includes(item).

  • You need to check if the current filter item is an array or not, and act accordingly.

  • In this code I didn't handle that and assumed that currentCandidate is a string or a primitive. If there are other cases where the candidate item (i.e. a field of the car) itself is also an array, then you have to update the code to also handle that.


  var cars = [
    {name: "BMW X5", topsales: "USA, China, Russia", maxspeed: 250, users: "teenage, ladies, men"},
    {name: "Volkswagen Touareg", topsales: "USA, Germany", maxspeed: 240, users: "teenage, men, old men"}
  ]
  
  var query = {
    topsales: ["USA", "China"],
    maxspeed: 240
  }
  
  function nestedFilter(targetArray, filters) {
    const filterKeys = Object.keys(filters);
    return targetArray.filter(function (eachObj) {
      //using some instead of every to make sure that it works as OR
      const result = filterKeys.some(function (eachKey) {
        //the current item that we are trying to use in the filter
        const currentCandidate = eachObj[eachKey];
  
        //the current item that we are using as a filter
        const currentFilterItem = filters[eachKey]
  
        if (Array.isArray(currentFilterItem)) {
          if (currentFilterItem.length === 0) {
            //no filter, return true
            return true
          }
  
          //loop on each item in the currentFilterItem
          //if any of them matches simply return true (OR)
          for (let filterKey in currentFilterItem) {
            if (currentCandidate.includes(currentFilterItem[filterKey])) {
              return true
            }
          }
          //for loop ended, no match
          return false
        } else {
          //the current filter item is not an array, use it as one item
          //return eachObj[eachKey].includes(currentFilterItem)
          return currentCandidate === currentFilterItem
        }
      });
  
      return result;
    });
  }
  
  goodresult = nestedFilter(cars, query);
  console.debug(goodresult)

CodePudding user response:

You can check if the value of the "filterKey" is not an array, make it an array, and check if an array has a subArray

function hasSubArray(master, sub) {
  return sub.every((i => v => i = master.indexOf(v, i)   1)(0));
}

function nestedFilter(targetArray, filters) {
  var filterKeys = Object.keys(filters);
  return targetArray.filter(function (eachObj) {
    return filterKeys.every(function (eachKey) {
      var subArray = filters[eachKey];
      if (!Array.isArray(filters[eachKey])) {
        subArray = [filters[eachKey]];
      }

      return hasSubArray(eachObj[eachKey], subArray);
    });
  });
}
  • Related