I have this sample code here, and I am trying to filter matching objects without exploding the code complexity or performance:
This code here filters matches based on one explicitly defined key and it's not case insensitive.
const people = [
{ firstName: 'Bob', lastName: 'Smith', status: 'single' },
{ firstName: 'bobby', lastName: 'Suxatcapitalizing', status: 'single' },
{ firstName: 'Jim', lastName: 'Johnson', status: 'complicated' },
{ firstName: 'Sally', lastName: 'Fields', status: 'relationship' },
{ firstName: 'Robert', lastName: 'Bobler', status: 'single' },
{ firstName: 'Johnny', lastName: 'Johannson', status: 'complicated' },
{ firstName: 'Whaley', lastName: 'McWhalerson', status: 'relationship'
rogueBonusKey: 'bob likes salmon' },
]
const searchString = 'Bob'
const found = people.filter((person) => {
if (person.firstName === searchString) return true
})
console.log(found)
THE GOAL:
contains
not exact matchSomething like this:
// const people = [
// { firstName: 'Bob', lastName: 'Smith', status: 'single' },
// { firstName: 'bobby', lastName: 'Suxatcapitalizing', status: 'single' },
// { firstName: 'Jim', lastName: 'Johnson', status: 'complicated' },
// { firstName: 'Sally', lastName: 'Fields', status: 'relationship' },
// { firstName: 'Robert', lastName: 'Bobler', status: 'single' },
// { firstName: 'Johnny', lastName: 'Johannson', status: 'complicated' },
// { firstName: 'Whaley', lastName: 'McWhalerson', status: 'relationship'
// rogueBonusKey: 'bob likes salmon' },
// ]
// const searchString = 'bob'
// ... magic
// console.log(found)
// { firstName: 'Bob', lastName: 'Smith', status: 'single' },
// { firstName: 'bobby', lastName: 'Suxatcapitalizing', status: 'single' },
// { firstName: 'Robert', lastName: 'Bobler', status: 'single' },
// { firstName: 'Whaley', lastName: 'McWhalerson', status: 'relationship'
// rogueBonusKey: 'bob likes salmon' },
I have scoured the documentations related to Array.filter()
and I can definitely make solutions that involve Array.reduce()
and looping over stuff with Object.keys(obj).forEach()
, but I want to know if there is a concise, performant way to handle this kind of fuzzy search.
Something like this:
const people = [
{ firstName: 'Bob', lastName: 'Smith', status: 'single' },
{ firstName: 'bobby', lastName: 'Suxatcapitalizing', status: 'single' },
{ firstName: 'Jim', lastName: 'Johnson', status: 'complicated' },
{ firstName: 'Sally', lastName: 'Fields', status: 'relationship' },
{ firstName: 'Robert', lastName: 'Bobler', status: 'single' },
{ firstName: 'Johnny', lastName: 'Johannson', status: 'complicated' },
{ firstName: 'Whaley', lastName: 'McWhalerson', status: 'relationship' },
rogueBonusKey: 'bob likes salmon' },
]
const searchString = 'Bob'
const found = people.filter((person) => {
if (person.toString().indexOf(searchString).toLowerCase !== -1) return true
})
console.log(found)
[edit] This definitely works, but is it acceptable?
const people = [
{ firstName: 'Bob', lastName: 'Smith', status: 'single' },
{ firstName: 'bobby', lastName: 'Suxatcapitalizing', status: 'single' },
{ firstName: 'Jim', lastName: 'Johnson', status: 'complicated' },
{ firstName: 'Sally', lastName: 'Fields', status: 'relationship' },
{ firstName: 'Robert', lastName: 'Bobler', status: 'single' },
{ firstName: 'Johnny', lastName: 'Johannson', status: 'complicated' },
{ firstName: 'Whaley', lastName: 'McWhalerson', status: 'relationship',
rogueBonusKey: 'bob likes salmon' },
]
const searchString = 'Bob'
const found = people.filter((person) => {
const savageMatch = JSON.stringify(person)
.toLowerCase()
.indexOf(searchString.toLowerCase()) !== -1
console.log(savageMatch)
if (savageMatch) return true
})
console.log(found)
Memory footprint optimized:
const found = people.filter((person) => JSON.stringify(person)
.toLowerCase()
.indexOf(searchString.toLowerCase()) !== -1
)
Converted to a function:
const fuzzyMatch = (collection, searchTerm) =>
collection.filter((obj) => JSON.stringify(obj)
.toLowerCase()
.indexOf(searchTerm.toLowerCase()) !== -1
)
console.log(fuzzyMatch(people, 'bob'))
There are some great answers in here; so far, I have selected this for my filtering needs:
const people = [
{ imageURL: 'http://www.alice.com/goat.jpeg', firstName: 'Bob', lastName: 'Smith', status: 'single' },
{ firstName: 'bobby', lastName: 'Suxatcapitalizing', status: 'single' },
{ firstName: 'Jim', lastName: 'Johnson', status: 'complicated' },
{ firstName: 'Sally', lastName: 'Fields', status: 'relationship' },
{ firstName: 'Robert', lastName: 'Bobler', status: 'single' },
{ firstName: 'Johnny', lastName: 'Johannson', status: 'complicated' },
{
firstName: 'Ronald', lastName: 'McDonlad', status: 'relationship',
rogueBonusKey: 'bob likes salmon'
},
{
imageURL: 'http://www.bob.com/cats.jpeg', firstName: 'Whaley', lastName: 'McWhalerson', status: 'relationship',
rogueBonusKey: 'bob hates salmon'
},
]
const searchString = 'bob'
const options = {
caseSensitive: false,
excludedKeys: ['imageURL', 'firstName'],
}
const customFind = (collection, term, opts) => {
const filterBy = () => {
const searchTerms = (!opts.caseSensitive) ? new RegExp(term, 'i') : new RegExp(term)
return (obj) => {
for (const key of Object.keys(obj)) {
if (searchTerms.test(obj[key]) &&
!opts.excludedKeys.includes(key)) return true
}
return false
}
}
return collection.filter(filterBy(term))
}
const found = customFind(people, searchString, options)
console.log(found)
I made it able to support case sensitivity and to exclude specific keys.
JavaScript objects don't have a filter() method, you must first turn the object into an array to use array's filter() method. You can use the Object. keys() function to convert the object's keys into an array, and accumulate the filtered keys into a new object using the reduce() function as shown below.
One can use filter() function in JavaScript to filter the object array based on attributes. The filter() function will return a new array containing all the array elements that pass the given condition. If no elements pass the condition it returns an empty array.
You can use Array.prototype.find()
.
const people = [
{ firstName: 'Bob', lastName: 'Smith', status: 'single' },
{ firstName: 'bobby', lastName: 'Suxatcapitalizing', status: 'single' },
{ firstName: 'Jim', lastName: 'Johnson', status: 'complicated' },
{ firstName: 'Sally', lastName: 'Fields', status: 'relationship' },
{ firstName: 'Robert', lastName: 'Bobler', status: 'single' },
{ firstName: 'Johnny', lastName: 'Johannson', status: 'complicated' },
{ firstName: 'Whaley', lastName: 'McWhalerson', status: 'relationship',
'rogueBonusKey': 'bob likes salmon' },
]
const searchString = 'Bob';
const person = people.find(({ firstName }) => firstName.toLowerCase() === searchString.toLowerCase());
console.log(person);
If we assume that all properties are strings, then you might do in the following way
const people = [
// ...
]
const searchString = 'Bob'
const filterBy = (term) => {
const termLowerCase = term.toLowerCase()
return (person) =>
Object.keys(person)
.some(prop => person[prop].toLowerCase().indexOf(termLowerCase) !== -1)
}
const found = people.filter(filterBy(searchString))
console.log(found)
Update: alternative solution with RegExp and more old-school :) but 2x faster
const people = [
// ...
]
const searchString = 'Bob'
const escapeRegExp = (str) => // or better use 'escape-string-regexp' package
str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&")
const filterBy = (term) => {
const re = new RegExp(escapeRegExp(term), 'i')
return person => {
for (let prop in person) {
if (!person.hasOwnProperty(prop)) {
continue;
}
if (re.test(person[prop])) {
return true;
}
}
return false;
}
}
const found = people.filter(filterBy(searchString))
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With