-
Notifications
You must be signed in to change notification settings - Fork 14
/
cryo.js
155 lines (133 loc) · 4.08 KB
/
cryo.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
154
155
/**
* JSON + Object references wrapper
*
* @author Hunter Loftis <hunter@skookum.com>
* @license The MIT license.
* @copyright Copyright (c) 2010 Skookum, skookum.com
*/
;(function() {
var CONTAINER_TYPES = {
'object': function() { return {}; },
'array': function() { return {}; },
'date': function() { return {}; },
'function': function() { return {}; }
};
var REFERENCE_FLAG = '_CRYO_REF_';
var INFINITY_FLAG = '_CRYO_INFINITY_';
var FUNCTION_FLAG = '_CRYO_FUNCTION_';
var UNDEFINED_FLAG = '_CRYO_UNDEFINED_';
var DATE_FLAG = '_CRYO_DATE_';
function typeOf(item) {
if (typeof item === 'object') {
if (item instanceof Array) return 'array';
if (item instanceof Date) return 'date';
return 'object';
}
return typeof item;
}
function stringify(item) {
var references = [];
var root = cloneWithReferences(item, references);
return JSON.stringify({
root: root,
references: references
});
}
function cloneWithReferences(item, references, savedItems) {
savedItems = savedItems || [];
var type = typeOf(item);
var constructor = CONTAINER_TYPES[type];
// can this object contain its own properties?
if (constructor) {
var referenceIndex = savedItems.indexOf(item);
// do we need to store a new reference to this object?
if (referenceIndex === -1) {
var clone = constructor();
for (var key in item) {
if (item.hasOwnProperty(key)) {
clone[key] = cloneWithReferences(item[key], references, savedItems);
}
}
referenceIndex = references.push({
contents: clone,
value: wrap(item)
}) - 1;
savedItems[referenceIndex] = item;
}
// return something like _CRYO_REF_22
return REFERENCE_FLAG + referenceIndex;
}
// return a non-container object
return wrap(item);
}
function parse(string) {
var json = JSON.parse(string);
return rebuildFromReferences(json.root, json.references);
}
function rebuildFromReferences(item, references) {
if (starts(item, REFERENCE_FLAG)) {
var referenceIndex = 0 | item.slice(REFERENCE_FLAG.length);
var ref = references[referenceIndex];
var container = unwrap(ref.value);
var contents = ref.contents;
for (var key in contents) {
container[key] = rebuildFromReferences(contents[key], references);
}
return container;
}
return unwrap(item);
}
function wrap(item) {
var type = typeOf(item);
if (type === 'undefined') return UNDEFINED_FLAG;
if (type === 'function') return FUNCTION_FLAG + item.toString();
if (type === 'date') return DATE_FLAG + item.getTime();
if (item === Infinity) return INFINITY_FLAG;
return item;
}
function unwrap(val) {
if (typeOf(val) === 'string') {
if (val === UNDEFINED_FLAG) return undefined;
if (starts(val, FUNCTION_FLAG)) {
var fn = val.slice(FUNCTION_FLAG.length);
var argStart = fn.indexOf('(') + 1;
var argEnd = fn.indexOf(')', argStart);
var args = fn.slice(argStart, argEnd);
var bodyStart = fn.indexOf('{') + 1;
var bodyEnd = fn.lastIndexOf('}') - 1;
var body = fn.slice(bodyStart, bodyEnd);
return new Function(args, body);
}
if (starts(val, DATE_FLAG)) {
var dateNum = parseInt(val.slice(DATE_FLAG.length), 10);
return new Date(dateNum);
}
if (val === INFINITY_FLAG) return Infinity;
}
return val;
}
function starts(string, prefix) {
return typeOf(string) === 'string' && string.slice(0, prefix.length) === prefix;
}
// Exported object
Cryo = {
stringify: stringify,
parse: parse
};
// global on server, window in browser
var root = this;
// AMD / RequireJS
if (typeof define !== 'undefined' && define.amd) {
define('Cryo', [], function () {
return Cryo;
});
}
// node.js
else if (typeof module !== 'undefined' && module.exports) {
module.exports = Cryo;
}
// included directly via <script> tag
else {
root.Cryo = Cryo;
}
})();