Smokey Dawson
Smokey Dawson

Reputation: 9230

filter an arrays of objects from an array of strings

I'm aware there are similar questions but none have been able to help me thus far - filtering an array of objects from and array of strings relies on you knowing the key value pair you would like to match, and same with this question here

Say I have an array of objects like so..

let users = [
    {
       name: 'Steve',
       age: 42,
       pets: {
           dog: 'spike';
       },
       favouriteFood: 'apples'
    },
    {
       name: 'Steve',
       age: 32,
       pets null
       favouriteFood: 'icecream'
    },
    {
       name: 'Jason',
       age: 31,
       pets: null
       favouriteFood: 'tacos'
    },
    {
       name: 'Jason',
       age: 31,
       pets: {
          cat: 'Jerry'
       },
       favouriteFood: 'bread'
    },
]

now I would like to be able to filter the array of users by matching a string in any of the objects keys. For example I want to filter out anyone whos name isnt 'steve' - keep in mind I may also want to filter out anyone who isnt 42 or who's favourite food isn't 'apples'

filter(term) {
    return objects.filter(x => {
        for(let key of Object.keys(x)) {
          if(typeof(x[key]) === 'object') {
              return JSON.stringify(x[key]).toLowerCase().includes(t);
          } else {
             return x[key].toString().toLowerCase().includes(t);
          }
        }
    });
}

now this function works but for only a single filter term

so If I ran filter('steve') I would then get

users = [
    {
       name: 'Steve',
       age: 42,
       pets: {
           dog: 'spike';
       },
       favouriteFood: 'apples'
    },
    {
       name: 'Steve',
       age: 32,
       pets null
       favouriteFood: 'icecream'
    }
]

as my result, but what If I wanted to filter out steve whos favourite food is apples?

I have tried to update my function as follows to loop through an array of terms and filter according to all the strings in the array

So I have tried

function filter(terms) {
    return term.forEach((t) => {
      return objects.filter(x => {
        for(let key of Object.keys(x)) {
          if(typeof(x[key]) === 'object') {
              return JSON.stringify(x[key]).toLowerCase().includes(t);
          } else {
             return x[key].toString().toLowerCase().includes(t);
          }
        }
      });
    });

but when I run filter(['steve', 'apples'])

I get undefined

My desired result would be

users = [
    {
       name: 'Steve',
       age: 42,
       pets: {
           dog: 'spike';
       },
       favouriteFood: 'apples'
    }
]

I'm not entirely sure what I'm doing wrong or how I could fix this function so it works correctly.

Any help would be appreciated.

Upvotes: 3

Views: 99

Answers (2)

StackSlave
StackSlave

Reputation: 10627

Sorry for the late response. I kept trying to come up with recursive code that would work in almost any situation. I eventually found some really cool snippets here, which is where I derived the similar function from equals, with slight considerations for compatibility.

function similar(a, b){
  if(a === b){
    return true;
  }
  if(a instanceof Date && b instanceof Date){
    return a.getTime() === b.getTime();
  }
  if (!a || !b || (typeof a !== 'object' && typeof b !== 'object')){
    return a === b;
  }
  if (a === null || a === undefined || b === null || b === undefined || a.prototype !== b.prototype){
    return false;
  }
  return Object.keys(b).every(function(k){
    return similar(a[k], b[k]);
  });
}
let users = [
    {
       name: 'Steve',
       age: 42,
       pets: {
           dog: 'spike'
       },
       favouriteFood: 'apples'
    },
    {
       name: 'Steve',
       age: 32,
       pets: null,
       favouriteFood: 'icecream'
    },
    {
       name: 'Jason',
       age: 31,
       pets: null,
       favouriteFood: 'tacos'
    },
    {
       name: 'Jason',
       age: 31,
       pets: {
          cat: 'Jerry'
       },
       favouriteFood: 'bread'
    }
]
var testObj = {name:'Jason', age: 31, pets:{cat:'Jerry'}};
for(var i=0,u,l=users.length; i<l; i++){
  u = users[i];
  if(similar(u, testObj)){
    console.log('contains testObj');
    console.log(u);
  }
  else if(!similar(u, {pets:null}) && !similar(u, {pets:{dog:'spot'}})){
    console.log('not spot');
    console.log(u);
  }
}

similar will see if anything matches exactly that is not an Object, or if it is an Object it will see if a contains b, considering that a and b have properties and values at the same depth and b doesn't consist of properties that do not exist in a.

Upvotes: 1

CertainPerformance
CertainPerformance

Reputation: 370759

Filter by whether .every value in the array of values needed is included in the Object.values of a given user:

const filter = arrOfValsNeeded => users.filter(user => {
  const vals = Object.values(user).map(val => typeof val === 'string' ? val.toLowerCase() : val);
  return arrOfValsNeeded.every(needed => vals.includes(needed.toLowerCase()));
});

let users = [
    {
       name: 'Steve',
       age: 42,
       pets: {
           dog: 'spike'
       },
       favouriteFood: 'apples'
    },
    {
       name: 'Steve',
       age: 32,
       pets: null,
       favouriteFood: 'icecream'
    },
    {
       name: 'Jason',
       age: 31,
       pets: null,
       favouriteFood: 'tacos'
    },
    {
       name: 'Jason',
       age: 31,
       pets: {
          cat: 'Jerry'
       },
       favouriteFood: 'bread'
    },
]

console.log(filter(['steve', 'apples']));

Or, if you need to recursively find all primitive values:

const allPrimitives = obj => {
  const primitives = [];
  JSON.stringify(obj, (key, val) => {
    if (typeof val !== 'object' || val === null) {
      primitives.push(typeof val === 'string' ? val.toLowerCase() : val);
    }
    return val;
  });
  return primitives;
};
const filter = arrOfValsNeeded => users.filter(user => {
  const vals = allPrimitives(user);
  return arrOfValsNeeded.every(needed => vals.includes(needed.toLowerCase()));
});

let users = [
    {
       name: 'Steve',
       age: 42,
       pets: {
           dog: 'spike'
       },
       favouriteFood: 'apples'
    },
    {
       name: 'Steve',
       age: 32,
       pets: null,
       favouriteFood: 'icecream'
    },
    {
       name: 'Jason',
       age: 31,
       pets: null,
       favouriteFood: 'tacos'
    },
    {
       name: 'Jason',
       age: 31,
       pets: {
          cat: 'Jerry'
       },
       favouriteFood: 'bread'
    },
]

console.log(filter(['steve', 'apples']));

If you need partial matches as well, use vals.some instead of vals.includes so you can identify substrings:

const allStrings = obj => {
  const strings = [];
  JSON.stringify(obj, (key, val) => {
    if (typeof val === 'string') {
      strings.push(val.toLowerCase());
    }
    return val;
  });
  return strings;
};
const filter = arrOfValsNeeded => {
  const lowerVals = arrOfValsNeeded.map(str => str.toLowerCase());
  return users.filter(user => {
    const existingStrings = allStrings(user);
    return lowerVals.every(
      lowerNeeded => existingStrings.some(
        existingString => existingString.includes(lowerNeeded)
      )
    );
  });
};

let users = [
    {
       name: 'Steve',
       age: 42,
       pets: {
           dog: 'spike'
       },
       favouriteFood: 'apples'
    },
    {
       name: 'Steve',
       age: 32,
       pets: null,
       favouriteFood: 'icecream'
    },
    {
       name: 'Jason',
       age: 31,
       pets: null,
       favouriteFood: 'tacos'
    },
    {
       name: 'Jason',
       age: 31,
       pets: {
          cat: 'Jerry'
       },
       favouriteFood: 'bread'
    },
]

console.log(filter(['steve', 'apples']));

Upvotes: 5

Related Questions