Keywords: JavaScript | array filtering | object search | ES6 | performance optimization
Abstract: This article provides an in-depth exploration of efficient techniques for filtering arrays of objects in JavaScript based on search keywords matching any property value. By analyzing multiple implementation approaches using native ES6 methods and the Lodash library, it compares code simplicity, performance characteristics, and appropriate use cases. The discussion begins with the core combination of Array.prototype.filter, Object.keys, Array.prototype.some, and String.prototype.includes, examines the JSON.stringify alternative and its potential risks, and concludes with performance optimization recommendations and practical application examples.
In modern web development, processing arrays of objects and filtering them based on dynamic search criteria is a common requirement. Users typically want to search across all properties without specifying particular attributes. This article systematically introduces several implementation methods and analyzes their advantages and limitations.
Core Implementation: ES6 Native Function Composition
The most straightforward and recommended approach combines multiple array and string methods provided by ES6. The core concept is: iterate through each object in the array and check whether any property value contains the search keyword.
function filterByValue(array, searchString) {
const lowerSearch = searchString.toLowerCase();
return array.filter(obj =>
Object.keys(obj).some(key => {
const value = obj[key];
return typeof value === 'string' &&
value.toLowerCase().includes(lowerSearch);
})
);
}
Let's break down this implementation:
array.filter(): Creates a new array with all elements that pass the testObject.keys(obj): Gets all enumerable property names of the object.some(): Tests whether at least one property satisfies the condition.toLowerCase(): Normalizes case for case-insensitive search.includes(): Checks if a string contains a substring
Example application:
const users = [
{ name: 'Paul', country: 'Canada' },
{ name: 'Lea', country: 'Italy' },
{ name: 'John', country: 'Italy' }
];
console.log(filterByValue(users, 'lea'));
// Output: [{name: 'Lea', country: 'Italy'}]
console.log(filterByValue(users, 'ita'));
// Output: [{name: 'Lea', country: 'Italy'}, {name: 'John', country: 'Italy'}]
Alternative Approach: JSON.stringify Method and Its Limitations
Another method uses JSON.stringify() to convert the entire object to a string before searching:
function filterByValueJSON(array, searchString) {
const lowerSearch = searchString.toLowerCase();
return array.filter(obj =>
JSON.stringify(obj).toLowerCase().includes(lowerSearch)
);
}
While this approach offers more concise code, it has significant drawbacks:
- Performance issues: Requires serializing the entire object in each iteration
- False matching risk: May match property names or JSON structural characters
- Type limitations: Cannot properly handle non-string properties (like numbers, booleans)
- Special character handling: May cause unexpected matches due to escape characters
Lodash Library Implementation
For projects already using Lodash, similar functionality can be achieved by combining multiple Lodash functions:
import _ from 'lodash';
function filterByValueLodash(array, searchString) {
const lowerSearch = searchString.toLowerCase();
return _.filter(array, obj =>
_.some(_.values(obj), value =>
_.isString(value) &&
_.toLower(value).includes(lowerSearch)
)
);
}
The Lodash version provides better type checking and error handling but adds library dependency.
Performance Optimization Considerations
In practical applications, especially when processing large arrays, performance optimization is crucial:
- Cache conversion results: If the search keyword remains unchanged, cache the
toLowerCase()result - Early exit: The
.some()method stops immediately after finding the first match, which is more efficient than.filter() - Type checking: Ensure search is performed only on string properties to avoid calling string methods on numbers, booleans, etc.
- Index optimization: For frequent search scenarios, consider building search indexes
Edge Case Handling
A robust implementation needs to consider various edge cases:
function robustFilterByValue(array, searchString) {
if (!Array.isArray(array) || !searchString) {
return [];
}
const lowerSearch = String(searchString).toLowerCase().trim();
return array.filter(obj => {
if (!obj || typeof obj !== 'object') return false;
return Object.keys(obj).some(key => {
const value = obj[key];
if (value == null) return false;
const stringValue = String(value);
return stringValue.toLowerCase().includes(lowerSearch);
});
});
}
This enhanced version handles: empty arrays, invalid search terms, null/undefined values, non-object elements, and other edge cases.
Practical Application Scenarios
This filtering technique is widely applied in:
- Real-time filtering for frontend search boxes
- Client-side filtering of data tables
- Autocomplete and suggestion features
- Log and monitoring data screening
When selecting an implementation approach, it's necessary to balance code simplicity, performance, and maintainability based on specific requirements. For most applications, the ES6 native method combination offers the best balance.