Bugs
Your code will throw errors if you include null
, or undefined
in the data.
And your code does not check for cyclic data structures.
First some review points.
Review
Some general review points regarding code style and language feature use.
Use RegExp
Rather than use String.includes convert the search term to a RegExp with the case insensitive flag. You can then test without the need to convert to lower case. Eg const match = new RegExp(value, "gi");
See example for more.
In this case RegExp has additional functionality that will help solve the bugs.
Use ===
not ==
Alwyas use === (Strict equality) and !== (Strict inequality) rather than == (Equality) and != (Inequality)
const
Use const for variables that do not change. For example the line for(let [key, val] of Object.entries(obj)){
should be for(const [key, val] of Object.entries(obj)){
Reduce code noise
Don't add else
/ else if
statements after you return
in the previous statement block.
Example
if (foo) {
return 0;
} else {
return 1;
}
// should be
if (foo) {
return 0;
}
return 1;
Use the language
Your code has not utilized appropriate language features.
You can use Array.some to check the content of an array of at least one match. That includes the array of entries from Object.entries
Rewrite
Thus it could be rewritten as follows..
function filterItem(item, value){
const match = new RegExp(value, "gi");
return (function filter(item){
if (item === null) { return false }
if (typeof item !== 'object') { return match.test(item) }
return Array.isArray(item) ?
item.some(item => filter(item)) :
Object.entries(item).some(([key, item]) => filter(key) || filter(item));
})(item);
}
The code checks for null
assuming that you will not be searching for such. This is needed because null is a type of Object
.
The code also does the string match for any item that is not an Object
this is because the call RegExp.test will automatically call the item's toString
function for you.
Cyclic bug
However the above code and your code has a dangerous bug that can cause the page to hang, and chew up a lot of memory (depending on the object being searched)
Take for example the following object..
const a = [];
a[0] = [a];
This will throw a call stack overflow because the data is cyclic.
Example of call stack overflow error
const a = [];
a[0] = [a];
console.log(filterItem(a, "c")); // Will throw
/****************************************************************/
/* Cleaned up version of original code as example of cyclic bug */
/****************************************************************/
function filterItem(item, value) {
if (typeof item == 'string') { return filterString(item, value) }
if (Array.isArray(item)) { return filterArray(item, value) }
return filterObj(item, value);
}
function filterObj(obj, value) {
for (const [key, val] of Object.entries(obj)) {
if (key.toLowerCase().includes(value)) { return true }
if (filterItem(val, value)) { return true }
}
return false;
}
function filterArray(array, value) {
for (const item of array) {
if (filterItem(item, value)) { return true }
}
return false;
}
function filterString(string, value) {
return string.toString().toLowerCase().includes(value);
}
Preventing call stack overflow
It is very important that you always protect against this situation. You can use a Set to check if you have encountered an object before you search it. Each time you search an item you first add it to the set.
Example using Set
const a = [];
a[0] = [a];
console.log(filterItem1(a, "c")); // Will throw
The following is protected against cyclic data but be warned. Any time you call a function in JS you run the risk of running out of space on the call stack.
function filterItem(item, value){
const match = new RegExp(value, "gi");
const tested = new Set();
return (function filter(item){
if (!tested.has(item)) {
tested.add(item);
if (item === null) { return false }
if (typeof item !== 'object') { return match.test(item) }
return Array.isArray(item) ?
item.some(item => filter(item)) :
Object.entries(item).some(([key, item]) => filter(key) || filter(item));
}
})(item);
}
You can improve the efficiency by using a WeakSet but you will need to ensure that you don`t try and add strings, numbers and anything that is not an Object
WeakSets do not store the object, only the reference to the hash and thus are quicker and use much less memory.
Example using WeakSet
Note that only objects are added to the weak set.
function filterItem(item, value){
const match = new RegExp(value, "gi");
const tested = new WeakSet();
return (function filter(item){
if (!tested.has(item)) {
if (item === null) { return false }
if (typeof item !== 'object') { return match.test(item) }
tested.add(item);
return Array.isArray(item) ?
item.some(item => filter(item)) :
Object.entries(item).some(([key, item]) => filter(key) || filter(item));
}
})(item);
}