UNPKG

@ericwong3/array-compress

Version:

Compress and compact array of objects, whether dense or sparse

140 lines (122 loc) 4.05 kB
function ArrayCompress(config){ this.config = config; } ArrayCompress.prototype.compress = function(data){ if(!Array.isArray(data)){ throw new Error('compress expects array'); } // collect all object keys var keys = []; data.forEach(function(item){ if(ArrayCompress.helpers.isRealObject(item)){ keys = keys.concat(Object.keys(item)); } }); // unique keys keys = keys.filter(function(v,i){ return keys.indexOf(v) === i }); var compressed = data.map(function(item){ if(ArrayCompress.helpers.isRealObject(item)){ var compressed = []; var fieldMap = []; var needFieldMap = false; keys.forEach(function(key, keyIndex){ if(item.hasOwnProperty(key)){ compressed.push(item[key]); fieldMap[keyIndex] = true; } else { fieldMap[keyIndex] = false; needFieldMap = true; } }); if(needFieldMap){ return { c: compressed, m: ArrayCompress.helpers.boolArrayToHex(fieldMap), } } else { return { c: compressed, } } } else { return { r: item, } } }); return { ks: keys, vs: compressed, }; } ArrayCompress.prototype.decompress = function(data){ if(typeof data !== 'object'){ throw new Error('data must be object'); return; } var keys = data.ks; var decompressed = data.vs.map(function(item){ var ret = {}; if(item.hasOwnProperty('r')){ ret = item.r; } else if(item.m) { // if containing field map var map = ArrayCompress.helpers.hexToBoolArray(item.m); var counter = 0; // count progress in processing `c` array map.forEach(function(bool, keyIndex){ if(bool){ ret[keys[keyIndex]] = item.c[counter++]; } }); } else { keys.forEach(function(key, i){ ret[key] = item.c[i]; }); } return ret; }); return decompressed; } ArrayCompress.helpers = { /** * Convert a hex string to an array of boolean, with less significant bit becomes start of array * E.g. '2c' (hex) => '00101100' (bin) => [f,f,t,f,t,t,f,f] (bool) => [f,f,t,t,f,t,f,f] (reversed, LSB first) */ hexToBoolArray: function(hexString){ return hexString.split('').reduce(function(acc, char){ return acc.concat( ('0000' + parseInt(char, 16).toString(2)).substr(-4).split('').map(function(c){return c == '1'}) ); }, []).reverse(); }, /** * Inverse of hexToBoolArray. Convert an array of boolean to hex string, with start of array becomes less significant bit * E.g. [f,f,t,t,f,t,f,f] => '2c' */ boolArrayToHex: function(_boolArray){ var boolArray = _boolArray.slice(); // copy for(var i = 0; i < ( 4 - boolArray.length % 4 ) % 4; i++){ // pad the array with false(0), at rightmost MSB, to 4n fields boolArray.push(false); } boolArray = boolArray.reverse(); // MSB to the left var ret = ''; for(var i = 0; i < boolArray.length; i += 4){ ret += parseInt( boolArray.slice(i, i + 4).map(function(b){return b ? '1' : '0'}).join(''), 2).toString(16); } return ret; }, /** * Returns whether `obj` is an "actual" object like {a: 1}, will return false for array and null */ isRealObject: function(obj){ return ( typeof obj === 'object' && obj !== null && !Array.isArray(obj) ); } } if(typeof module === 'object'){ module.exports = ArrayCompress; } else { this['ArrayCompress'] = ArrayCompress; }