-
Notifications
You must be signed in to change notification settings - Fork 6
/
Copy pathstringifyPrepare.js
153 lines (128 loc) · 4.12 KB
/
stringifyPrepare.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
140
141
142
143
144
145
146
147
148
149
150
151
152
153
'use strict';
const markerKey = Symbol('warp10');
const isArray = Array.isArray;
class Marker {
constructor(path, symbol) {
this.path = path;
this.symbol = symbol;
}
}
function append(array, el) {
var len = array.length;
var clone = new Array(len+1);
for (var i=0; i<len; i++) {
clone[i] = array[i];
}
clone[len] = el;
return clone;
}
class Assignment {
constructor(lhs, rhs) {
this.l = lhs;
this.r = rhs;
}
}
function handleProperty(clone, key, value, valuePath, serializationSymbol, assignments) {
if (value.constructor === Date) {
assignments.push(new Assignment(valuePath, { type: 'Date', value: value.getTime() }));
} else if (isArray(value)) {
const marker = value[markerKey];
if (marker && marker.symbol === serializationSymbol) {
assignments.push(new Assignment(valuePath, marker.path));
} else {
value[markerKey] = new Marker(valuePath, serializationSymbol);
clone[key] = pruneArray(value, valuePath, serializationSymbol, assignments);
}
} else {
const marker = value[markerKey];
if (marker && marker.symbol === serializationSymbol) {
assignments.push(new Assignment(valuePath, marker.path));
} else {
value[markerKey] = new Marker(valuePath, serializationSymbol);
clone[key] = pruneObject(value, valuePath, serializationSymbol, assignments);
}
}
}
function pruneArray(array, path, serializationSymbol, assignments) {
let len = array.length;
var clone = new Array(len);
for (let i=0; i<len; i++) {
var value = array[i];
if (value == null) {
continue;
}
if (value && typeof value === 'object') {
handleProperty(clone, i, value, append(path, i), serializationSymbol, assignments);
} else {
clone[i] = value;
}
}
return clone;
}
function pruneObject(obj, path, serializationSymbol, assignments) {
var clone = {};
if (obj.toJSON && obj.constructor != Date) {
obj = obj.toJSON();
}
if (typeof obj !== 'object') {
return obj;
}
// `Object.keys(...)` with standard for loop is faster than `for in` in v8
var keys = Object.keys(obj);
var len = keys.length;
for (var i = 0; i < len; i++) {
var key = keys[i];
var value = obj[key];
if (value === undefined) {
continue;
}
if (value && typeof value === 'object') {
handleProperty(clone, key, value, append(path, key), serializationSymbol, assignments);
} else {
clone[key] = value;
}
}
return clone;
}
module.exports = function stringifyPrepare(obj) {
if (!obj) {
return obj;
}
/**
* Performance notes:
*
* - It is faster to use native JSON.stringify instead of a custom stringify
* - It is faster to first prune and then call JSON.stringify with _no_ replacer
*/
var pruned;
const assignments = []; // Used to keep track of code that needs to run to fix up the stringified object
if (typeof obj === 'object') {
if (obj.toJSON && obj.constructor != Date) {
obj = obj.toJSON();
if (!obj.hasOwnProperty || typeof obj !== 'object') {
return obj;
}
}
const serializationSymbol = Symbol(); // Used to detect if the marker is associated with _this_ serialization
const path = [];
obj[markerKey] = new Marker(path, serializationSymbol);
if (obj.constructor === Date) {
pruned = null;
assignments.push(new Assignment([], { type: 'Date', value: obj.getTime() }));
} else if (isArray(obj)) {
pruned = pruneArray(obj, path, serializationSymbol, assignments);
} else {
pruned = pruneObject(obj, path, serializationSymbol, assignments);
}
} else {
pruned = obj;
}
if (assignments.length) {
return {
o: pruned,
$$: assignments
};
} else {
return pruned;
}
};