-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathjsonext.js
237 lines (217 loc) · 10.7 KB
/
jsonext.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
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
/*
JSONext library. Copyright (C) techspider 2019.
Licensed under GNU GPLv3.
JSON definitions retrieved from MDN doc:
- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify
- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse
*/
(function() {
var validTransformations = ["date", "symbol", "escape", "regexp"];
var compressIgnoreChars = ["\n", "\r", " "];
var _JSON = JSON;
/**
* @exports JSONExt
*/
var _JSONext = {
/**
* Defines a value transformation.
* @param {String} type The transformation type.
* @param {String} expression The expression to use for the transformation.
*/
defineVT: function(type, expression) {
if((type == null) || (expression == null))
throw new Error("Invalid arguments provided.");
else if(typeof type !== 'string')
throw new Error("Type for argument 'type' must be a String.");
if(!validTransformations.includes(type.toLowerCase()))
throw new Error(`The transformation "${type}" is not a valid transformation.`);
return `@T(${type}, [${expression}])`;
},
/**
* Resolves an object from a value transformation expression.
* @param {String} t_string The expression to resolve.
*/
resolveFromTStr: function(t_string) {
if(t_string == null)
throw new Error("Invalid arguments provided.");
else if(typeof t_string !== 'string')
throw new Error("Type for argument 't_string' must be a String.");
else if((!t_string.startsWith("(")) && (!t_string.endsWith(")")))
throw new SyntaxError("Invalid transformation string provided.");
var transformation = t_string;
var value = undefined;
transformation = transformation.substring(3, transformation.length - 1);
var expectedType = transformation.substr(0, transformation.indexOf(',')).toLowerCase();
var expression = transformation.substring(transformation.indexOf("[")+1, transformation.lastIndexOf("]"));
switch(expectedType) {
default:
throw new Error(`Transformation "${expectedType}" is not supported.`);
case "date":
value = new Date(expression);
break;
case "regexp":
value = new RegExp(expression);
break;
case "escape":
value = expression;
break;
case "symbol":
switch(expression.toLowerCase()) {
default:
throw new Error(`"${expression}" is not a recognized symbol specifier.`);
case "nan":
value = NaN;
break;
case "infinity":
value = Infinity;
break;
}
break;
}
return value;
},
/**
* Converts a JavaScript object or value to a JSON string.
* @param {*} value The value to convert to a JSON string.
* @param {Function|Array} [replacer] A function that alters the behavior of the stringification process, or an array of `String` and `Number` objects that serve as a whitelist for selecting/filtering the properties of the value object to be included in the JSON string.
* @param {String|Number} [space] A `String` or `Number` object that's used to insert white space into the output JSON string for readability purposes.
*/
stringify: function(value, replacer, space) {
if(value == null)
throw new Error("Invalid arguments provided.");
if((replacer != null) && (typeof replacer !== 'function'))
return _JSON.stringify(value, replacer, space);
else return _JSON.stringify(value, function(_key, _value) {
var key = _key;
var value = _value;
if(Number.isNaN(value))
value = _JSONext.defineVT("Symbol", "NaN");
else if(value instanceof Date)
value = _JSONext.defineVT("Date", value.toUTCString());
else if(value instanceof RegExp)
value = _JSONext.defineVT("RegExp", value.source);
else if((typeof value === 'string') && value.startsWith("@T"))
value = _JSONext.defineVT("Escape", value);
else if(value == Infinity)
value = _JSONext.defineVT("Symbol", "Infinity");
return (replacer == null) ? value : replacer(key, value);
}, space);
},
/**
* Compresses the size of a JSONext string by removing trailing whitespaces, comments, etc.
* @param {String} text The JSONext string to compress.
*/
compress: function(text) {
if(text == null)
throw new Error("Invalid arguments provided.");
else if(typeof text !== 'string')
throw new Error("Type for argument 'text' must be a String.");
var compressed = "";
var textfmt = this.process(text); //Remove comments
var escape = false;
var ltm = 0;
for(var x = 0; x < textfmt.length; x++) {
if(escape) { escape = false; }
if((ltm == 1) && (textfmt[x] == '\\')) {
escape = true;
compressed += textfmt[x] + textfmt[x+1];
x += 1;
continue;
}
if((ltm == 0) && compressIgnoreChars.includes(textfmt[x])) continue;
compressed += textfmt[x];
if(textfmt[x] == '"') {
if((ltm == 1) && (!escape)) {
ltm = 0;
continue;
}
ltm = 1;
continue;
}
}
return compressed;
},
/**
* Formats a JSONext string to standard JSON.
* @param {String} text The string to process as JSONext.
*/
process: function(text) {
if(text == null)
throw new Error("Invalid arguments provided.");
else if(typeof text !== 'string')
throw new Error("Type for argument 'text' must be a String.");
var procText = text;
var litType = 0;
var escape = false;
// Process the JSON document. Check for single line comments, etc.
for(var x = 0; x < procText.length; x++) {
if(procText[x] == "\\") { // Check if character is escape character
if(escape) { // If \ character is being escaped, do not treat it as an escape character.
escape = false;
continue;
}
escape = true;
continue;
} else if((procText[x] == '"') && (litType != 1)) { // Check if we are encountering a string literal.
litType = 1; // Switch interpreting mode.
continue;
} else if((procText[x] == '"') && (litType == 1)) { // Check if we encounter a closing quote. It must not be an escape character.
if(escape) continue; // Ignore escape char
litType = 0;
continue;
}
if(escape) { // Skip char, turn off escape mode if it has been enabled.
escape = false;
continue;
}
if((x + 1) < procText.length) {
if(litType != 0) continue; // Do not process literals.
if((procText[x] == '/') && (procText[x + 1] == '/')) { // Single line comment found, ignore
var nextNewLine = procText.indexOf('\n', x);
if(nextNewLine == -1) nextNewLine = (procText.length);
var t1 = procText.substring(0, x);
var t2 = procText.substring(nextNewLine, procText.length);
procText = t1 + t2;
} else if((procText[x] == '/') && (procText[x + 1] == '*')) {
var closingStrIndex = procText.indexOf('*/', x);
if(closingStrIndex == -1) throw new SyntaxError("Failed to parse JSONext, missing matching */ for multi line comment at char " + (x+1));
var t1 = procText.substring(0, x);
var t2 = procText.substring(closingStrIndex+2, procText.length);
procText = t1 + t2;
x = x - 1;
}
}
}
return procText;
},
/**
* Parses the specified JSONext string constructing the JavaScript value or object described by the string.
* @param {String} text The string to parse as JSONext.
* @param {Function} [reviver] If a function, this prescribes how the value originally produced by parsing is transformed, before being returned.
* @returns The Object corresponding to the given JSON text.
* @throws Throws a `SyntaxError` exception if the string to parse is not valid JSON.
*/
parse: function(text, reviver) {
if(text == null)
throw new Error("Invalid arguments provided.");
return _JSON.parse(this.process(text), function(_key, _value) {
var key = _key;
var value = _value;
// Check for JSONext transformation specifiers in string
if((typeof value === 'string') && value.startsWith("@T")) {
// Parse transformation specifier.
var transformation = value.trim();
if((!transformation.startsWith("(")) && (!transformation.endsWith(")"))) throw new SyntaxError("Incomplete transformation specifier for property \"" + key + "\"");
var tt = undefined;
if((tt = _JSONext.resolveFromTStr(transformation)) !== undefined)
value = tt;
}
return (reviver == null) ? value : reviver(key, value);
});
},
[Symbol.toStringTag]: "JSONext"
};
if(typeof window !== 'undefined')
window.JSONext = _JSONext;
else module.exports = _JSONext;
})();