Querying and filtering JavaScript objects and arrays

Another powerful feature of Mangler.js is checking if an object or value satisfies a test expression. A test expression is a javascript object literal with special properties, very similar to and inspired by MongoDB‘s .find() method.

Using test expressions, there are a number of different methods which help you filter and extract parts of your collection, as well as finding the first or last item that passes the test. Here are some quick examples:

var stock = [
	{
		id: 1001,
		name: 'Apple',
		price: 0.50,
		qty: 150,
		tags: ['fruit', 'red'],
		expiry: new Date('2014-06-16')
	},
	{
		id: 1002,
		name: 'Banana',
		price: 1.20,
		qty: 30,
		tags: ['fruit', 'yellow', 'seedless'],
		expiry: new Date('2014-07-10')
	},
	{
		id: 1003,
		name: 'Cherry',
		price: 2.50,
		qty: 220,
		tags: ['fruit', 'red'],
		expiry: new Date('2014-06-19')
	},
	{
		id: 1004,
		name: 'Grapes',
		price: 0.80,
		qty: 130,
		tags: ['fruit', 'green', 'seedless'],
		expiry: new Date('2014-07-12')
	}
];

// Find cherries by ID and return object
var cherry = Mangler(stock).first({ id: 1003 });

// Find all red fruit and put them in an array
var red_fruit = Mangler.find(stock, { tags: ['red', 'fruit'] });

// Create mangler object then narrow down list to cheap seedless fruit
var m = Mangler(stock).filter({ price: { $lt: 1.00 }, tags: ['seedless'] });

// Register custom Date getter for querying the number of days left
Mangler.registerType(Date, {
	get: function(date, key) {
		if(key === 'daysLeft') {
			var oneDay = 24 * 60 * 60 * 1000; // Milliseconds in a day
			return (date.getTime() - Date.now() + Date.now() % oneDay) / oneDay;
		}
	}
});

// Get all items that expire in the next 7 days
// As of 2014-06-15, matching items are apple and cherry
var thisWeek = Mangler.find(stock, { 'expiry.daysLeft' : { $gte: 0, $lt: 7 } });

Functions and methods

Before going deeper into the test expressions themselves, here is a list of all the functions and methods of the Mangler.js library that can be used for querying and testing. In the following spec, <value> is any javascript value, like a string, number, or object. Test expressions will be marked by <test>.

All functions and methods use Mangler.test(<value>, <test>) internally, which returns true if a single <value> satisfies the <test> expression.

Static methods of the library for testing items contained in any iterable object, such as arrays:

  • Mangler.find(<iterable>, <test>) returns an array of all items in <iterable> that passes the <test>.
  • Mangler.first(<iterable>, <test>) returns the first item of <iterable> that passes the <test>.
  • Mangler.last(<iterable>, <test>) returns the last item of <iterable> that passes the <test>.

Alternatively, you can use mangler objects to easily do multiple operations on the same set of data. They are simple object containers created and returned by the Mangler(<items>) function. On a mangler object var mg = Mangler(data), you can use the following methods:

  • mg.find(<test>) returns an array of all items in the mangler object that passes the <test>.
  • mg.first(<test>) returns the first item of the mangler object that passes the <test>.
  • mg.last(<test>) returns the last item of the mangler object that passes the <test>.
  • mg.filter(<test>) removes all items from the mangler object that doesn’t pass the <test> and returns a reference to the mangler object itself for method chaining.

I showed you in the Mangler.js introduction post how to find an item in an array by creating an index on an object property. While it is useful if you do multiple lookups on the list, for one-off searches use one of the above .first() methods, which should be more efficient.

Test expression

A <test> expression can be one of the following:

  • Any simple value can be a test expression in itself and it’s evaluated using the strict equality operator ===. For example Mangler.test(5, 5) returns true, while Mangler.test(5, '5') returns false.
  • A regular expression, which evaluates to true if it matches the tested string. Mangler.test('hello', /^h/) returns true.
  • An object literal with special properties. All properties are evaluated with an implied and logical relationship.
  • An array is interpreted as an implied $all operator.
  • function is interpreted as an implied $where operator.

Here’s a complete list of the valid properties of a test object:

  • $gt, $gte, $lt, $lte$eq and $ne are simple comparisons, corresponding to >, >=, <, <==== and !== respectively. Use it as { $gt: <value> }
    • You can also use any comparison operator directly as a property name, e.g. '<'. Note that this is the only way to use the non-strict operators '==' and '!='. Thanks to Kapa for his input on this feature. Use it as { '==': <value> }
  • $or is true if any test expression in the passed array evaluates to true. { $or: [<test>, <test>, ...] }
  • $all is true if the tested array contains all values passed. { $all [<value>, <value>, ...] }
  • $size is true if the tested array has matching length. { $size: <value> }
  • $elemMatch is true if any item in the tested array satisfies the test expression. { $elemMatch: <test> }
  • $in is true if the tested array contains at least one passed value. If tested against a single value, it’s true if value is in the passed array. { $in: [ <value>, <value>, ...] }
  • $not is the logical opposite of the passed test expression. { $not: <test> }
  • $nin is shorthand for { $not: { $in: [ <value>, <value>, ...] } }
  • $nor is shorthand for { $not: { $or: [<test>, <test>, ...] } }
  • $exists evaluates to the value passed if the tested value is not undefined. { $exists: true/false }
  • $type checks the type with Mangler.compareType().
  • $mod is true if value % divisor === remainder. Use as { $mod: [divisor, remainder] }
  • $where evaluates to the return value of the passed function, called with the tested value as parameter. { $where: function }
  • All other property names will be interpreted as either a named property on the tested object, or a path into the tested object. It will be evaluated using Mangler.getPath(), then tested. Use it as { property: <test> } or { 'path': <test> }

Properties and paths

Mangler.get(<object>, <string/number>) gets a single named property or array item from <object>.

Mangler.getPath(<object>, <path>) splits the <path> parameter to tokens and recursively calls Mangler.get() on <object>.

var data = {
	array: [{ value: 'example' }],
	'special.property': true
};

// The following all return &quot;example&quot;
Mangler.getPath(data, 'array[0].value');
Mangler.getPath(data.array, '[0].value');
Mangler.getPath(data.array[0], 'value');

// The following returns undefined, as it splits the path
Mangler.getPath(data, 'special.property');

// The following returns true, as it directly reads the named property
Mangler.get(data, 'special.property');

Other object types and calculated properties

Mangler.js by default knows how to use .get() on simple objects and arrays, but as with cloning and iterators, there is a way to register a custom .get() function for other object types.

The .get() function should usually compliment the .each() function, but it is not necessary. In fact it is a great way to implement hidden or calculated properties that won’t be iterated by .each(), but can be directly accessed by .get() and included in queries or used in indices as if they were real object properties.

The following example will extend the Date object with custom getter and shows how to use it in queries:

Mangler.mergeType(Date, {
	get: function(date, prop) {
		switch(prop) {
			case 'day':    return date.getDate();
			case 'month':  return date.getMonth() + 1;
			case 'year':   return date.getFullYear();
		}
	}
});

var people = [
	{ name: 'John', dob: new Date('1982-12-03') },
	{ name: 'Fred', dob: new Date('1983-03-20') },
	{ name: 'Bill', dob: new Date('1984-12-07') },
	{ name: 'Jack', dob: new Date('1982-11-10') }
];

// Find people born in 1982 (John, Jack)
var in82 = Mangler.find(people, { 'dob.year': 1982 });

// Find people born in first quarter (Fred)
var q1 = Mangler.find(people, { 'dob.month': { $lte: 3 } });

// Find people born in December (John, Bill)
var dec = Mangler.find(people, { dob: { month: 12 } });

// Find people born in an odd year (Fred)
var odd = Mangler.find(people, { 'dob.year': { $mod: [2, 1] } });
Share Button
Tagged with: ,

Leave a Reply