2

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.

2
  • What do you want returned from the search ? The whole top level object that was matched ? Commented Feb 15, 2019 at 17:52
  • @GabrielePetrioli Yes, an array with all the matching (top level) objects Commented Feb 15, 2019 at 17:58

2 Answers 2

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: "[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);

Sign up to request clarification or add additional context in comments.

Comments

0

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;

}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.