-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathindex.js
139 lines (122 loc) · 3.96 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
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;
}