问题:
I've got an array with nested objects in it. Something like this:
const results = [
{
general: {
orderID: '5567',
created: 1548765626...
可以将文章内容翻译成中文,广告屏蔽插件会导致该功能失效:
问题:
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: 'info@companyx.com',
companyContact: 'John Doe'
},
customer: {
customerName: 'Jane Doe',
customerEmail: 'janedoe@email.com'
},
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.
回答1:
You need to iterate over the results
array and then deep search each object for matching items. For that you will need to
- get all the key/value pairs
- if value is object, search deeper
- if value is array search each item deeper
- otherwise (value is string or number)
- if key is in the list of fields to search
- if value is matched to the query return true
- otherwise return false
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: "info@companyx.com",
companyContact: "John Doe"
},
customer: {
customerName: "Jane Doe",
customerEmail: "janedoe@email.com"
},
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);
回答2:
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;
}