Source: object/collectionContains.js

/**
 * Checks if an array of objetcs contain another object (even partially)
 * @todo don't support nested objects for now
 *
 * @param {Object[]} collection
 * @param {Object} userFilters object to find
 * @param {Boolean} [detailed] if true will return an object with deails about the searc
 * @param {Boolean} [ignoreCase] if true, will ignore case when matching strings
 * @return {Boolean|Object}
 * @memberof object
 *
 * @example single filter
 * const fruits = [{ name: 'banana', weight: 200, color: 'yellow' }, { name: 'tomato', weight: 150, color: 'red' }]
 * collectionContains(fruits, { name: 'banana' }); // will return true
 *
 * @example multiple filters
 * const fruits = [{ name: 'banana', weight: 200, color: 'yellow' }, { name: 'tomato', weight: 150, color: 'red' }]
 * collectionContains(fruits, { name: 'banana', color: 'red' }); // will return false
 *
 * @example custom comparison logic
 * const fruits = [{ name: 'banana', weight: 200, color: 'yellow' }, { name: 'tomato', weight: 150, color: 'red' }]
 * collectionContains(fruits, { weight: v => v > 100 }); // will return true
 */
function collectionContains (collection, userFilters, detailed = false, ignoreCase = false) {
  const filters = Object.assign({}, userFilters);
  const details = [];
  const found = collection.some((obj) => {
    const diff = {};

    let expectedValue;
    let realValue;

    Object.keys(filters).forEach(filter => {
      expectedValue = filters[filter];
      realValue = obj[filter];

      if (ignoreCase === true) {
        if (typeof expectedValue === 'string' || expectedValue instanceof String) {
          expectedValue = expectedValue.toLowerCase();
        }

        if (typeof realValue === 'string' || realValue instanceof String) {
          realValue = realValue.toLowerCase();
        }
      }

      if (typeof filters[filter] === 'function') {
        if (!filters[filter](realValue)) {
          diff[filter] = {
            expected: 'custom validation', // @todo
            real: realValue,
          };
        }
      } else if (!Object.prototype.hasOwnProperty.call(obj, filter) || expectedValue != realValue) {
        diff[filter] = {
          expected: expectedValue,
          real: realValue,
        };
      }
    });

    details.push(diff);
    return Object.keys(diff).length === 0;
  });

  if (!detailed) {
    return found;
  }

  return {
    found,
    details: details,
  };
}

collectionContains.Types = {
  NotEmpty: function (v) {
    return !!v;
  },
};

module.exports = collectionContains;