简体   繁体   中英

Filter an array based on dynamic keys in an array of nested objects

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.

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);

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;

}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM