UNPKG

obop

Version:

MongoDB-style object operators makes array manipulation easy: where/order/update/view

231 lines (200 loc) 5.33 kB
/*! view.js */ /** * This filters fields of items, or generates a projection function compiled from view mapping parameters object. * Since version 0.0.6, dot notation (e.g. foo.bar.baz) is supported to handle child nodes. * * @method obop.prototype.view * @param {Array} [array] - source array to map * @param {Object|Function} param - output fields or function * @returns {Array} Array of filtered items (if source array given) * @returns {Function} Projection function compiled for Array.prototype.map() method (if source array not given) * @returns {Error} Error instance (if source array not given but invalid output fields specified) * * @example * var list = [ * { name: "apple", price: 50 }, * { name: "orange", price: 10 }, * { name: "pineapple", price: 70 }, * { name: "grape", price: 30 } * ]; * * // map without obop * var out1 = list.map(function(item) { * return { name: item.name }; * }); * console.log(out1); * * // map with obop * var view = { name: 1 }; * var out2 = obop.view(list, view); * console.log(out2); * * // compile a map function * var view = { name: 1 }; * var filter = obop.view(view); * if (filter instanceof Error) throw filter; * var out3 = list.map(filter); * console.log(out3); */ exports.view = function(array, param) { var func; var len = arguments.length; if (len === 1) { func = view.call(this, array); if (func instanceof Error) throw func; return func; } else if (len === 2) { if (array instanceof Array) { func = view.call(this, param); if (func instanceof Error) throw func; return array.map(func); } else { throw new Error('Invalid argument type: ' + array); } } else { throw new Error('Invalid arguments length: ' + len); } }; function view(param) { var obop = this; var _nop = null; // no operation // function type if ('function' === typeof param) { return param; } // default parameters param = param || {}; // other types than object if ('object' !== typeof param) { return new Error('Invalid view parameters type: ' + param); } var queries = Object.keys(param); // no param: all properties if (!queries.length) { return _nop; } var key0 = queries[0]; var truthy = {}; var falsy = {}; var dottruthy = {}; var dotfalsy = {}; var hasdot; // group parameters for (var key in param) { if (/(^|\.)__proto__(\.|$)/.test(key)) { return new Error('Invalid target: ' + key); } var val = param[key]; var pos1 = key.indexOf('.'); if (pos1 > -1) { var pre = key.substr(0, pos1); var post = key.substr(pos1 + 1); hasdot = true; if (val) { // {"foo.bar": 1} dottruthy[pre] = dottruthy[pre] || {}; dottruthy[pre][post] = val; } else { // {"foo.bar": 0} dotfalsy[pre] = dotfalsy[pre] || {}; dotfalsy[pre][post] = val; } } else if (val) { // {"foo": 1} truthy[key] = true; } else { // {"bar": 0} falsy[key] = true; } } if (hasdot) { Object.keys(dottruthy).forEach(function(key) { var param = dottruthy[key]; truthy[key] = obop.view(param); }); Object.keys(dotfalsy).forEach(function(key) { var param = dotfalsy[key]; falsy[key] = obop.view(param); }); } var _truthy = Object.keys(truthy).length; var _falsy = Object.keys(falsy).length; // single truthy field: faster if (_truthy === 1 && !_falsy && !hasdot) { return single_view; } if (_truthy && !_falsy) { return hasdot ? truthy_has_dot : truthy_view; } if (!_truthy && _falsy) { return hasdot ? falsy_has_dot : falsy_view; } return truthy_and_falsy; function single_view(item) { var out = {}; if (item.hasOwnProperty(key0)) { out[key0] = item[key0]; } return out; } // negative filter function falsy_view(item) { var out = {}; Object.keys(item).forEach(function(key) { if (!falsy[key]) { out[key] = item[key]; } }); return out; } // positive filter function truthy_view(item) { var out = {}; Object.keys(item).forEach(function(key) { if (truthy[key]) { out[key] = item[key]; } }); return out; } // negative filter (with dot) function falsy_has_dot(item) { var out = {}; Object.keys(item).forEach(function(key) { var view = falsy[key]; var val = item[key]; if ('function' === typeof view) { if ('object' === typeof val) { out[key] = view(val); } else { out[key] = val; } } else if (!view) { out[key] = val; } }); return out; } // positive filter (with dot) function truthy_has_dot(item) { var out = {}; Object.keys(item).forEach(function(key) { var view = truthy[key]; var val = item[key]; if ('function' === typeof view) { if ('object' === typeof val) { out[key] = view(val); } } else if (view) { out[key] = val; } }); return out; } // both positive and negative filters function truthy_and_falsy(item) { if (_truthy) item = truthy_has_dot(item); if (_falsy) item = falsy_has_dot(item); return item; } }