Reputation: 1838
I've got an array with nested objects in it. Something like this:
const results = [
{
general: {
orderID: '5567',
created: 1548765626101,
status: 'new'
},
company: {
companyName: 'company x',
companyEmail: '[email protected]',
companyContact: 'John Doe'
},
customer: {
customerName: 'Jane Doe',
customerEmail: '[email protected]'
},
products: [
{
productID: 4765756,
productName: 'Product x',
productDescription: 'Description for product x'
},
{
productID: 4767839,
productName: 'Product y',
productDescription: 'Description for product y'
}
],
payment: {
price: 1000,
method: 'cash'
}
},
]
(To keep it a little bit structured I only inserted one result object for this question. But let's say there are 100 elements in the results array.)
A user is able to type in a search term and check/uncheck keys that will include or exclude these keys. The keys are hardcoded in a list.
So for example. A user types in 'jane' and checks customerName and customerEmail as the wanted keys to search. Or a user types in 'x' and checks productName.
How can I dynamically search into these checked keys? I'm already having the selected keys in an array.
So for the first example, I've got ['customerName', 'customerEmail']
.
For the second one it's ['productName']
I have used array.filter()
before for hardcoded keys but I have no clue on how to filter for these dynamic keys.
Can someone help me out with a breakdown of the different steps? I'm working with es6, without external libraries.
Upvotes: 2
Views: 1677
Reputation: 156
Maybe something like this? Keep in mind that 'searchTerm' is type-sensitive.
Usage : search( results, ['companyName', 'productName'], 'x' );
/**
* Returns an array of objects which contains at least one 'searchKey' whose value
* matches THE 'searchTerm'.
*/
function search( inp, searchKeys, searchTerm ) {
let retArray = [];
function rdp( inp, searchKeys, searchTerm ) {
if ( Array.isArray(inp) ) {
if (inp.length > 0) {
inp.forEach(elem => {
rdp( elem, searchKeys, searchTerm );
});
}
}
else {
Object.keys( inp ).forEach( prop => {
if ( Array.isArray( inp[ prop ] ) || ( typeof inp[ prop ] == 'object')) {
rdp( inp[ prop ], searchKeys, searchTerm );
}
else {
searchKeys.forEach( key => {
if (( prop == key ) && // key match
( prop in inp)) { // search term found
switch ( typeof inp[prop] ) {
case 'string' : if (inp[ prop ].indexOf( searchTerm ) > -1) { retArray.push( inp ); } break;
case 'number' : if ( inp[ prop ] === searchTerm ) { retArray.push( inp ); } break;
}
}
});
}
});
}
}
rdp( inp, searchKeys, searchTerm );
return retArray;
}
Upvotes: 0
Reputation: 195972
You need to iterate over the results
array and then deep search each object for matching items. For that you will need to
Something along the lines of
const deepSearcher = (fields, query) =>
function matcher(object) {
const keys = Object.keys(object);
return keys.some(key => {
const value = object[key];
// handle sub arrays
if (Array.isArray(value)) return value.some(matcher);
// handle sub objects
if (value instanceof Object) return matcher(value);
// handle testable values
if (fields.includes(key)) {
// handle strings
if (typeof value === "string") return value.includes(query);
// handle numbers
return value.toString() === query.toString();
}
return false;
});
};
This function creates a matcher to be used with the .filter
method.
const customerFilter = deepSearcher(['customerName', 'customerEmail'], 'jane')
const found = results.filter(customerFilter);
or you can pass it directly to the .filter
const found = results.filter(deepSearcher(['customerName', 'customerEmail'], 'jane'));
The fields you pass to deepSearcher do not have to belong to the same object. The matcher will test anything for a match (but they have to point to string/numbers for this code to work).
Working test cases
const results = [{
general: {
orderID: "5567",
created: 1548765626101,
status: "new"
},
company: {
companyName: "company x",
companyEmail: "[email protected]",
companyContact: "John Doe"
},
customer: {
customerName: "Jane Doe",
customerEmail: "[email protected]"
},
products: [{
productID: 4765756,
productName: "Product x",
productDescription: "Description for product x"
},
{
productID: 4767839,
productName: "Product y",
productDescription: "Description for product y"
}
],
payment: {
price: 1000,
method: "cash"
}
}];
const deepSearcher = (fields, query) =>
function matcher(object) {
const keys = Object.keys(object);
return keys.some(key => {
const value = object[key];
// handle sub arrays
if (Array.isArray(value)) return value.some(matcher);
// handle sub objects
if (value instanceof Object) return matcher(value);
// handle testable values
if (fields.includes(key)) {
// handle strings
if (typeof value === "string") return value.includes(query);
// handle numbers
return value.toString() === query.toString();
}
return false;
});
};
const matchingCustomer = results.filter(deepSearcher(["customerName", "customerEmail"], 'jane'));
console.log('results with matching customer:', matchingCustomer.length);
const matchingProduct = results.filter(deepSearcher(["productName"], 'x'));
console.log('results with matching product:', matchingProduct.length);
const matchingPrice = results.filter(deepSearcher(["price"], '1000'));
console.log('results with matching price:', matchingPrice.length);
const nonMatchingPrice = results.filter(deepSearcher(["price"], '500'));
console.log('results with non matching price:', nonMatchingPrice.length);
Upvotes: 1