quick-json5 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +7 -0
- package/README.md +66 -0
- package/dist/exports/Exports.d.ts +6 -0
- package/dist/exports/Exports.d.ts.map +1 -0
- package/dist/exports/Exports.js +6 -0
- package/dist/exports/Exports.js.map +1 -0
- package/dist/json/JsonEncoder.d.ts +3 -0
- package/dist/json/JsonEncoder.d.ts.map +1 -0
- package/dist/json/JsonEncoder.js +147 -0
- package/dist/json/JsonEncoder.js.map +1 -0
- package/dist/json/JsonParser.d.ts +3 -0
- package/dist/json/JsonParser.d.ts.map +1 -0
- package/dist/json/JsonParser.js +378 -0
- package/dist/json/JsonParser.js.map +1 -0
- package/dist/json5/Json5Encoder.d.ts +3 -0
- package/dist/json5/Json5Encoder.d.ts.map +1 -0
- package/dist/json5/Json5Encoder.js +155 -0
- package/dist/json5/Json5Encoder.js.map +1 -0
- package/dist/json5/Json5Parser.d.ts +3 -0
- package/dist/json5/Json5Parser.d.ts.map +1 -0
- package/dist/json5/Json5Parser.js +781 -0
- package/dist/json5/Json5Parser.js.map +1 -0
- package/dist/tests/Json5EncoderTest.d.ts +2 -0
- package/dist/tests/Json5EncoderTest.d.ts.map +1 -0
- package/dist/tests/Json5EncoderTest.js +44 -0
- package/dist/tests/Json5EncoderTest.js.map +1 -0
- package/dist/tests/Json5ParserTests.d.ts +3 -0
- package/dist/tests/Json5ParserTests.d.ts.map +1 -0
- package/dist/tests/Json5ParserTests.js +211 -0
- package/dist/tests/Json5ParserTests.js.map +1 -0
- package/dist/tests/JsonEncoderTests.d.ts +2 -0
- package/dist/tests/JsonEncoderTests.d.ts.map +1 -0
- package/dist/tests/JsonEncoderTests.js +44 -0
- package/dist/tests/JsonEncoderTests.js.map +1 -0
- package/dist/tests/JsonParserTests.d.ts +3 -0
- package/dist/tests/JsonParserTests.d.ts.map +1 -0
- package/dist/tests/JsonParserTests.js +140 -0
- package/dist/tests/JsonParserTests.js.map +1 -0
- package/dist/tests/Test.d.ts +2 -0
- package/dist/tests/Test.d.ts.map +1 -0
- package/dist/tests/Test.js +9 -0
- package/dist/tests/Test.js.map +1 -0
- package/dist/types/Types.d.ts +8 -0
- package/dist/types/Types.d.ts.map +1 -0
- package/dist/types/Types.js +2 -0
- package/dist/types/Types.js.map +1 -0
- package/dist/unused/Unused.d.ts +2 -0
- package/dist/unused/Unused.d.ts.map +1 -0
- package/dist/unused/Unused.js +47 -0
- package/dist/unused/Unused.js.map +1 -0
- package/dist/utilities/JsonParseError.d.ts +7 -0
- package/dist/utilities/JsonParseError.d.ts.map +1 -0
- package/dist/utilities/JsonParseError.js +10 -0
- package/dist/utilities/JsonParseError.js.map +1 -0
- package/dist/utilities/ObjectUtilities.d.ts +2 -0
- package/dist/utilities/ObjectUtilities.d.ts.map +1 -0
- package/dist/utilities/ObjectUtilities.js +142 -0
- package/dist/utilities/ObjectUtilities.js.map +1 -0
- package/dist/utilities/TypedArray.d.ts +4 -0
- package/dist/utilities/TypedArray.d.ts.map +1 -0
- package/dist/utilities/TypedArray.js +2 -0
- package/dist/utilities/TypedArray.js.map +1 -0
- package/dist/utilities/Utilities.d.ts +17 -0
- package/dist/utilities/Utilities.d.ts.map +1 -0
- package/dist/utilities/Utilities.js +97 -0
- package/dist/utilities/Utilities.js.map +1 -0
- package/package.json +38 -0
- package/src/exports/Exports.ts +7 -0
- package/src/json/JsonEncoder.ts +180 -0
- package/src/json/JsonParser.ts +491 -0
- package/src/json5/Json5Encoder.ts +189 -0
- package/src/json5/Json5Parser.ts +959 -0
- package/src/tests/Json5EncoderTest.ts +51 -0
- package/src/tests/Json5ParserTests.ts +237 -0
- package/src/tests/JsonEncoderTests.ts +51 -0
- package/src/tests/JsonParserTests.ts +159 -0
- package/src/tests/Test.ts +13 -0
- package/src/types/Types.ts +9 -0
- package/src/unused/Unused.ts +51 -0
- package/src/utilities/JsonParseError.ts +7 -0
- package/src/utilities/ObjectUtilities.ts +184 -0
- package/src/utilities/TypedArray.ts +39 -0
- package/src/utilities/Utilities.ts +130 -0
- package/tsconfig.json +55 -0
|
@@ -0,0 +1,491 @@
|
|
|
1
|
+
import { JsonReviverFunction } from '../types/Types.js'
|
|
2
|
+
import { JsonParserError } from '../utilities/JsonParseError.js'
|
|
3
|
+
import { getPositionInfo, hexCharCodeToNumber, positivePowersOf10 } from '../utilities/Utilities.js'
|
|
4
|
+
|
|
5
|
+
export function parseJSON(jsonString: string, reviver?: JsonReviverFunction) {
|
|
6
|
+
if (typeof jsonString !== 'string') {
|
|
7
|
+
throw new TypeError(`Given JSON string argument is not a string.`)
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
if (reviver !== undefined && typeof reviver !== 'function') {
|
|
11
|
+
throw new TypeError(`Reviver can only be a function.`)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
let readPosition = 0
|
|
15
|
+
|
|
16
|
+
function parse(initialCharCode: number) {
|
|
17
|
+
////////////////////////////////////////////////////////////////////////////////////////////
|
|
18
|
+
// Parse string
|
|
19
|
+
////////////////////////////////////////////////////////////////////////////////////////////
|
|
20
|
+
if (initialCharCode === 34) { // '"' for string start
|
|
21
|
+
readPosition += 1
|
|
22
|
+
|
|
23
|
+
// Try to quickly parse the string for the common case where there are no escape patterns
|
|
24
|
+
{
|
|
25
|
+
const match = jsonString.substring(readPosition).match(/^[^\\\x00-\x1F]*?"/)
|
|
26
|
+
|
|
27
|
+
if (match !== null) {
|
|
28
|
+
const matchString = match[0]
|
|
29
|
+
|
|
30
|
+
const str = matchString.substring(0, matchString.length - 1)
|
|
31
|
+
|
|
32
|
+
readPosition += matchString.length
|
|
33
|
+
|
|
34
|
+
return str
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Fallback to slower method if an escape pattern is found or string is unterminated.
|
|
39
|
+
let decodedString = ''
|
|
40
|
+
|
|
41
|
+
while (true) {
|
|
42
|
+
const charCode = readCharCodeAndAdvance()
|
|
43
|
+
|
|
44
|
+
if (charCode === 34) { // '"'
|
|
45
|
+
break
|
|
46
|
+
} else if (charCode === 92) { // '\\'
|
|
47
|
+
const escapedCharCharcode = readCharCodeAndAdvance()
|
|
48
|
+
|
|
49
|
+
if (escapedCharCharcode <= 92) {
|
|
50
|
+
if (escapedCharCharcode === 34 || escapedCharCharcode === 92 || escapedCharCharcode === 47) { // '"', '\' and '/'
|
|
51
|
+
decodedString += String.fromCharCode(escapedCharCharcode)
|
|
52
|
+
} else {
|
|
53
|
+
const positionInfo = getInfoForPosition(readPosition - 2)
|
|
54
|
+
throw new JsonParserError(`Invalid escaped character '${String.fromCharCode(escapedCharCharcode)}' in escape sequence starting at ${positionInfo.positionString}.`, positionInfo)
|
|
55
|
+
}
|
|
56
|
+
} else if (escapedCharCharcode === 110) { // 'n'
|
|
57
|
+
decodedString += '\n'
|
|
58
|
+
} else if (escapedCharCharcode === 114) { // 'r'
|
|
59
|
+
decodedString += '\r'
|
|
60
|
+
} else if (escapedCharCharcode === 116) { // 't'
|
|
61
|
+
decodedString += '\t'
|
|
62
|
+
} else if (escapedCharCharcode === 117) { // 'u'
|
|
63
|
+
if (readPosition + 4 > jsonString.length) {
|
|
64
|
+
const positionInfo = getInfoForCurrentReadPosition()
|
|
65
|
+
throw new JsonParserError(`Unterminated hexadecimal sequence. Expected 4 hexadecimal characters at ${positionInfo.positionString}.`, positionInfo)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const codePoint =
|
|
69
|
+
hexCharCodeToNumber(jsonString.charCodeAt(readPosition + 0)) << 12 |
|
|
70
|
+
hexCharCodeToNumber(jsonString.charCodeAt(readPosition + 1)) << 8 |
|
|
71
|
+
hexCharCodeToNumber(jsonString.charCodeAt(readPosition + 2)) << 4 |
|
|
72
|
+
hexCharCodeToNumber(jsonString.charCodeAt(readPosition + 3))
|
|
73
|
+
|
|
74
|
+
decodedString += String.fromCharCode(codePoint)
|
|
75
|
+
|
|
76
|
+
readPosition += 4
|
|
77
|
+
} else if (escapedCharCharcode === 102) { // 'f'
|
|
78
|
+
decodedString += '\f'
|
|
79
|
+
} else if (escapedCharCharcode === 98) { // 'b'
|
|
80
|
+
decodedString += '\b'
|
|
81
|
+
} else {
|
|
82
|
+
const positionInfo = getInfoForPosition(readPosition - 2)
|
|
83
|
+
throw new JsonParserError(`Invalid escaped character '${String.fromCharCode(escapedCharCharcode)}' in escape sequence at ${positionInfo.positionString}.`, positionInfo)
|
|
84
|
+
}
|
|
85
|
+
} else if (charCode === undefined) {
|
|
86
|
+
const positionInfo = getInfoForPosition(jsonString.length - 1)
|
|
87
|
+
throw new JsonParserError(`Unterminated string literal.`, positionInfo)
|
|
88
|
+
} else if (charCode < 32) {
|
|
89
|
+
const positionInfo = getInfoForPosition(readPosition - 1)
|
|
90
|
+
throw new JsonParserError(`Invalid unescaped control character (${charCode}) at ${positionInfo.positionString}.`, positionInfo)
|
|
91
|
+
} else {
|
|
92
|
+
decodedString += String.fromCharCode(charCode)
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return decodedString
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
////////////////////////////////////////////////////////////////////////////////////////////
|
|
100
|
+
// Parse number
|
|
101
|
+
////////////////////////////////////////////////////////////////////////////////////////////
|
|
102
|
+
if (initialCharCode < 91) { // '0'-'9' or '-' for number start
|
|
103
|
+
let numberStringStartPosition = readPosition
|
|
104
|
+
let charCode = initialCharCode
|
|
105
|
+
|
|
106
|
+
let isNegative: boolean
|
|
107
|
+
|
|
108
|
+
if (charCode === 45) { // '-'
|
|
109
|
+
isNegative = true
|
|
110
|
+
|
|
111
|
+
charCode = advanceAndReadCharCode()
|
|
112
|
+
} else {
|
|
113
|
+
isNegative = false
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
let concatenatedInteger = 0
|
|
117
|
+
|
|
118
|
+
// Parse integer part
|
|
119
|
+
{
|
|
120
|
+
const digitsStartPosition = readPosition
|
|
121
|
+
|
|
122
|
+
while (charCode >= 48 && charCode <= 57) { // '0'-'9'
|
|
123
|
+
const digitValue = charCode - 48
|
|
124
|
+
|
|
125
|
+
concatenatedInteger = (concatenatedInteger * 10) + digitValue
|
|
126
|
+
|
|
127
|
+
charCode = advanceAndReadCharCode()
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const integerPartDigitCount = readPosition - digitsStartPosition
|
|
131
|
+
|
|
132
|
+
if (integerPartDigitCount === 0) {
|
|
133
|
+
const positionInfo = getInfoForCurrentReadPosition()
|
|
134
|
+
throw new JsonParserError(`Invalid character '${String.fromCharCode(charCode)}' at ${positionInfo.positionString}.`, positionInfo)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (integerPartDigitCount > 1 && jsonString[digitsStartPosition] === '0') {
|
|
138
|
+
const positionInfo = getInfoForPosition(digitsStartPosition)
|
|
139
|
+
throw new JsonParserError(`Invalid leading zero found in number literal at ${positionInfo.positionString}.`, positionInfo)
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
let fractionalDigitCount = 0
|
|
144
|
+
|
|
145
|
+
// Parse fractional part
|
|
146
|
+
if (charCode === 46) { // '.'
|
|
147
|
+
charCode = advanceAndReadCharCode()
|
|
148
|
+
|
|
149
|
+
const digitsStartPosition = readPosition
|
|
150
|
+
|
|
151
|
+
while (charCode >= 48 && charCode <= 57) { // '0'-'9'
|
|
152
|
+
const digitValue = charCode - 48
|
|
153
|
+
concatenatedInteger = (concatenatedInteger * 10) + digitValue
|
|
154
|
+
|
|
155
|
+
charCode = advanceAndReadCharCode()
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
fractionalDigitCount = readPosition - digitsStartPosition
|
|
159
|
+
|
|
160
|
+
if (fractionalDigitCount === 0) {
|
|
161
|
+
const positionInfo = getInfoForCurrentReadPosition()
|
|
162
|
+
throw new JsonParserError(`Invalid character '${String.fromCharCode(charCode)}' at ${positionInfo.positionString}. Exepcted at least one decimal digit following '.'.`, positionInfo)
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
let exponent = 0
|
|
167
|
+
|
|
168
|
+
// Parse exponent part
|
|
169
|
+
if (charCode === 101 || charCode === 69) { // 'e' or 'E'
|
|
170
|
+
charCode = advanceAndReadCharCode()
|
|
171
|
+
|
|
172
|
+
let isNegativeExponent = false
|
|
173
|
+
|
|
174
|
+
if (charCode === 43) { // '+'
|
|
175
|
+
charCode = advanceAndReadCharCode()
|
|
176
|
+
} else if (charCode === 45) { // '-'
|
|
177
|
+
isNegativeExponent = true
|
|
178
|
+
|
|
179
|
+
charCode = advanceAndReadCharCode()
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
let exponentDigitsStartPosition = readPosition
|
|
183
|
+
|
|
184
|
+
while (charCode >= 48 && charCode <= 57) { // '0'-'9'
|
|
185
|
+
const digitValue = charCode - 48
|
|
186
|
+
exponent = (exponent * 10) + digitValue
|
|
187
|
+
|
|
188
|
+
charCode = advanceAndReadCharCode()
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (readPosition === exponentDigitsStartPosition) {
|
|
192
|
+
const positionInfo = getInfoForCurrentReadPosition()
|
|
193
|
+
throw new JsonParserError(`Invalid character '${String.fromCharCode(charCode)}' at ${positionInfo.positionString}. Exepcted at least one exponent digit.`, positionInfo)
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (isNegativeExponent) {
|
|
197
|
+
exponent = -exponent
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const adjustedExponent = exponent - fractionalDigitCount
|
|
202
|
+
|
|
203
|
+
// Use fast method for cases where the number can be parsed efficiently,
|
|
204
|
+
// or fall back to slower method if not possible.
|
|
205
|
+
if (concatenatedInteger < 2 ** 53 && adjustedExponent >= -22 && adjustedExponent <= 22) {
|
|
206
|
+
let parsedNumber = concatenatedInteger
|
|
207
|
+
|
|
208
|
+
if (adjustedExponent > 0) {
|
|
209
|
+
parsedNumber *= positivePowersOf10[adjustedExponent]
|
|
210
|
+
} else if (adjustedExponent < 0) {
|
|
211
|
+
parsedNumber /= positivePowersOf10[-adjustedExponent]
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (isNegative) {
|
|
215
|
+
return -parsedNumber
|
|
216
|
+
} else {
|
|
217
|
+
return parsedNumber
|
|
218
|
+
}
|
|
219
|
+
} else {
|
|
220
|
+
return Number(jsonString.substring(numberStringStartPosition, readPosition))
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
////////////////////////////////////////////////////////////////////////////////////////////
|
|
225
|
+
// Parse 'true' literal
|
|
226
|
+
////////////////////////////////////////////////////////////////////////////////////////////
|
|
227
|
+
if (initialCharCode === 116) { // 't' for true
|
|
228
|
+
if (
|
|
229
|
+
jsonString.charCodeAt(readPosition + 1) !== 114 || // 'r'
|
|
230
|
+
jsonString.charCodeAt(readPosition + 2) !== 117 || // 'u'
|
|
231
|
+
jsonString.charCodeAt(readPosition + 3) !== 101) { // 'e'
|
|
232
|
+
|
|
233
|
+
const positionInfo = getInfoForCurrentReadPosition()
|
|
234
|
+
throw new JsonParserError(`Expected 'true' at ${positionInfo.positionString}.`, positionInfo)
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
readPosition += 4
|
|
238
|
+
|
|
239
|
+
return true
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
////////////////////////////////////////////////////////////////////////////////////////////
|
|
243
|
+
// Parse 'false' literal
|
|
244
|
+
////////////////////////////////////////////////////////////////////////////////////////////
|
|
245
|
+
if (initialCharCode === 102) { // 'f' for false
|
|
246
|
+
if (
|
|
247
|
+
jsonString.charCodeAt(readPosition + 1) !== 97 || // 'a'
|
|
248
|
+
jsonString.charCodeAt(readPosition + 2) !== 108 || // 'l'
|
|
249
|
+
jsonString.charCodeAt(readPosition + 3) !== 115 || // 's'
|
|
250
|
+
jsonString.charCodeAt(readPosition + 4) !== 101) { // 'e'
|
|
251
|
+
|
|
252
|
+
const positionInfo = getInfoForCurrentReadPosition()
|
|
253
|
+
throw new JsonParserError(`Expected 'false' at ${positionInfo.positionString}.`, positionInfo)
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
readPosition += 5
|
|
257
|
+
|
|
258
|
+
return false
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
////////////////////////////////////////////////////////////////////////////////////////////
|
|
262
|
+
// Parse 'null' literal
|
|
263
|
+
////////////////////////////////////////////////////////////////////////////////////////////
|
|
264
|
+
if (initialCharCode === 110) { // 'n' for null
|
|
265
|
+
if (
|
|
266
|
+
jsonString.charCodeAt(readPosition + 1) !== 117 || // 'u'
|
|
267
|
+
jsonString.charCodeAt(readPosition + 2) !== 108 || // 'l'
|
|
268
|
+
jsonString.charCodeAt(readPosition + 3) !== 108) { // 'l'
|
|
269
|
+
|
|
270
|
+
const positionInfo = getInfoForCurrentReadPosition()
|
|
271
|
+
throw new JsonParserError(`Expected 'null' at ${positionInfo.positionString}.`, positionInfo)
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
readPosition += 4
|
|
275
|
+
|
|
276
|
+
return null
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
////////////////////////////////////////////////////////////////////////////////////////////
|
|
280
|
+
// Parse array
|
|
281
|
+
////////////////////////////////////////////////////////////////////////////////////////////
|
|
282
|
+
if (initialCharCode === 91) { // '[' for array start
|
|
283
|
+
readPosition += 1
|
|
284
|
+
|
|
285
|
+
let charCode = skipToNextReadableCharCode()
|
|
286
|
+
|
|
287
|
+
if (charCode === 93) { // ']' for array end
|
|
288
|
+
readPosition += 1
|
|
289
|
+
|
|
290
|
+
return []
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
const arr: any[] = []
|
|
294
|
+
|
|
295
|
+
while (true) {
|
|
296
|
+
const elementStartPosition = readPosition
|
|
297
|
+
|
|
298
|
+
let element = parse(charCode)
|
|
299
|
+
|
|
300
|
+
if (reviver !== undefined) {
|
|
301
|
+
element = applyReviver('', element, elementStartPosition)
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
arr.push(element)
|
|
305
|
+
|
|
306
|
+
charCode = skipToNextReadableCharCode()
|
|
307
|
+
|
|
308
|
+
if (charCode === 44) { // ',' for comma
|
|
309
|
+
readPosition += 1
|
|
310
|
+
|
|
311
|
+
charCode = skipToNextReadableCharCode()
|
|
312
|
+
|
|
313
|
+
continue
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
if (charCode === 93) { // ']' for array end
|
|
317
|
+
readPosition += 1
|
|
318
|
+
|
|
319
|
+
break
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
{
|
|
323
|
+
const positionInfo = getInfoForCurrentReadPosition()
|
|
324
|
+
throw new JsonParserError(`Invalid character '${String.fromCharCode(charCode)}' in array expression at ${positionInfo.positionString}. Expected ',' or ']'.`, positionInfo)
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
return arr
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
////////////////////////////////////////////////////////////////////////////////////////////
|
|
332
|
+
// Parse object
|
|
333
|
+
////////////////////////////////////////////////////////////////////////////////////////////
|
|
334
|
+
if (initialCharCode === 123) { // '{' for object start
|
|
335
|
+
readPosition += 1
|
|
336
|
+
|
|
337
|
+
let charCode = skipToNextReadableCharCode()
|
|
338
|
+
|
|
339
|
+
if (charCode === 125) { // '}' for object end
|
|
340
|
+
readPosition += 1
|
|
341
|
+
|
|
342
|
+
return {}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
const obj: any = {}
|
|
346
|
+
|
|
347
|
+
while (true) {
|
|
348
|
+
if (charCode !== 34) {
|
|
349
|
+
const positionInfo = getInfoForCurrentReadPosition()
|
|
350
|
+
throw new JsonParserError(`Invalid character '${String.fromCharCode(charCode)}' at ${positionInfo.positionString}. Expected '"'.`, positionInfo)
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
const key = parse(charCode)
|
|
354
|
+
|
|
355
|
+
charCode = skipToNextReadableCharCode()
|
|
356
|
+
|
|
357
|
+
if (charCode !== 58) { // ':'
|
|
358
|
+
const positionInfo = getInfoForCurrentReadPosition()
|
|
359
|
+
throw new JsonParserError(`Invalid character '${String.fromCharCode(charCode)}' at ${positionInfo.positionString}. Expected ':'.`, positionInfo)
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
readPosition += 1
|
|
363
|
+
|
|
364
|
+
charCode = skipToNextReadableCharCode()
|
|
365
|
+
|
|
366
|
+
const valueStartPosition = readPosition
|
|
367
|
+
|
|
368
|
+
let value = parse(charCode)
|
|
369
|
+
|
|
370
|
+
if (reviver !== undefined) {
|
|
371
|
+
value = applyReviver(key, value, valueStartPosition, obj)
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
obj[key] = value
|
|
375
|
+
|
|
376
|
+
const commaOrClosingBraceCharCode = skipToNextReadableCharCode()
|
|
377
|
+
|
|
378
|
+
if (commaOrClosingBraceCharCode === 44) { // ','
|
|
379
|
+
readPosition += 1
|
|
380
|
+
|
|
381
|
+
charCode = skipToNextReadableCharCode()
|
|
382
|
+
|
|
383
|
+
continue
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
if (commaOrClosingBraceCharCode === 125) { // '}'
|
|
387
|
+
readPosition += 1
|
|
388
|
+
|
|
389
|
+
break
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
{
|
|
393
|
+
const positionInfo = getInfoForCurrentReadPosition()
|
|
394
|
+
throw new JsonParserError(`Invalid character '${String.fromCharCode(commaOrClosingBraceCharCode)}' in object expression at ${positionInfo.positionString}. Expected ',' or '}'.`, positionInfo)
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
return obj
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
{
|
|
402
|
+
const positionInfo = getInfoForCurrentReadPosition()
|
|
403
|
+
throw new JsonParserError(`Invalid character '${String.fromCharCode(initialCharCode)}' at ${positionInfo.positionString}.`, positionInfo)
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
function advanceAndReadCharCode() {
|
|
408
|
+
return jsonString.charCodeAt(++readPosition)
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
function readCharCodeAndAdvance() {
|
|
412
|
+
return jsonString.charCodeAt(readPosition++)
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
function skipWhitespace() {
|
|
416
|
+
while (readPosition < jsonString.length) {
|
|
417
|
+
const charCode = jsonString.charCodeAt(readPosition)
|
|
418
|
+
|
|
419
|
+
if (charCode > 32 || (charCode !== 32 && charCode !== 10 && charCode !== 13 && charCode !== 9)) {
|
|
420
|
+
return charCode
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
readPosition += 1
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
return undefined
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
function skipToNextReadableCharCode() {
|
|
430
|
+
const nextCharCode = skipWhitespace()
|
|
431
|
+
|
|
432
|
+
if (nextCharCode === undefined) {
|
|
433
|
+
const positionInfo = getInfoForPosition(jsonString.length - 1)
|
|
434
|
+
throw new JsonParserError(`Unexpected termination of JSON input.`, positionInfo)
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
return nextCharCode
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
function getInfoForCurrentReadPosition() {
|
|
441
|
+
return getInfoForPosition(readPosition)
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
function getInfoForPosition(position: number) {
|
|
445
|
+
return getPositionInfo(jsonString, position)
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
function applyReviver(key: string, value: any, valueStartPosition: number, thisArg?: any) {
|
|
449
|
+
if (reviver === undefined) {
|
|
450
|
+
return
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
let context: string | undefined
|
|
454
|
+
|
|
455
|
+
if (typeof value !== 'object' || value === null) {
|
|
456
|
+
context = jsonString.substring(valueStartPosition, readPosition)
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
let result: any
|
|
460
|
+
|
|
461
|
+
if (thisArg) {
|
|
462
|
+
result = reviver.call(thisArg, key, value, context)
|
|
463
|
+
} else {
|
|
464
|
+
result = reviver(key, value, context)
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
return result
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
// Parse the given string
|
|
471
|
+
{
|
|
472
|
+
const initialCharCode = skipToNextReadableCharCode()
|
|
473
|
+
|
|
474
|
+
const documentStartPosition = readPosition
|
|
475
|
+
|
|
476
|
+
let result = parse(initialCharCode)
|
|
477
|
+
|
|
478
|
+
if (reviver !== undefined) {
|
|
479
|
+
result = applyReviver('', result, documentStartPosition)
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
const finalSkipResult = skipWhitespace()
|
|
483
|
+
|
|
484
|
+
if (finalSkipResult !== undefined) {
|
|
485
|
+
const positionInfo = getInfoForCurrentReadPosition()
|
|
486
|
+
throw new JsonParserError(`Unexpected trailing character(s) starting at ${positionInfo.positionString}.`, positionInfo)
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
return result
|
|
490
|
+
}
|
|
491
|
+
}
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import { Json5Options, JsonReplacerFunction, JsonReplacerType } from '../types/Types.js'
|
|
2
|
+
import { charCodeToHex } from '../utilities/Utilities.js'
|
|
3
|
+
|
|
4
|
+
export function stringify(obj: any, replacer?: JsonReplacerType, space = 0, options?: Json5Options) {
|
|
5
|
+
const extensionsEnabled = options?.enableExtensions
|
|
6
|
+
|
|
7
|
+
let replacerFunc: JsonReplacerFunction | undefined
|
|
8
|
+
|
|
9
|
+
if (replacer != null) {
|
|
10
|
+
if (typeof replacer === 'function') {
|
|
11
|
+
replacerFunc = replacer
|
|
12
|
+
} else if (Array.isArray(replacer)) {
|
|
13
|
+
const replacerSet = new Set()
|
|
14
|
+
|
|
15
|
+
for (const key of replacer) {
|
|
16
|
+
replacerSet.add(String(key))
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
replacerFunc = (key, value) => {
|
|
20
|
+
if (replacerSet.has(key)) {
|
|
21
|
+
return value
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
} else {
|
|
25
|
+
throw new TypeError('Invalid replacer argument.')
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const baseIndentString = ' '.repeat(space)
|
|
30
|
+
|
|
31
|
+
let currentIndentLevel = 0
|
|
32
|
+
|
|
33
|
+
function getIndentString() {
|
|
34
|
+
if (space === 0) {
|
|
35
|
+
return ''
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const spaces = baseIndentString.repeat(currentIndentLevel)
|
|
39
|
+
|
|
40
|
+
return `\n${spaces}`
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function encode(obj: any) {
|
|
44
|
+
if (obj === undefined) {
|
|
45
|
+
return ''
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const typeofObj = typeof obj
|
|
49
|
+
|
|
50
|
+
if (obj === null || typeofObj === 'number' || typeofObj === 'boolean') {
|
|
51
|
+
return String(obj)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (typeofObj === 'bigint') {
|
|
55
|
+
if (extensionsEnabled === false) {
|
|
56
|
+
throw new Error(`Encoding BigInt values requires JSON5 extensions to be enabled in options.`)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return String(obj)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (typeofObj === 'string') {
|
|
63
|
+
return `"${escapeStringIfNeeded(obj)}"`
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (typeof obj !== 'object') {
|
|
67
|
+
throw new Error(`Type '${typeof obj}' can't be encoded in JSON5.`)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (typeof obj.toJSON === 'function') {
|
|
71
|
+
return obj.toJSON()
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (Array.isArray(obj)) {
|
|
75
|
+
let str = '['
|
|
76
|
+
let isFirstElement = true
|
|
77
|
+
|
|
78
|
+
for (let i = 0; i < obj.length; i++) {
|
|
79
|
+
const element = obj[i]
|
|
80
|
+
|
|
81
|
+
if (element === undefined) {
|
|
82
|
+
continue
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
currentIndentLevel += 1
|
|
86
|
+
|
|
87
|
+
const saparatingComma = isFirstElement === true ? '' : ','
|
|
88
|
+
const indentString = getIndentString()
|
|
89
|
+
|
|
90
|
+
const encodedElement = encode(element)
|
|
91
|
+
|
|
92
|
+
str += `${saparatingComma}${indentString}${encodedElement}`
|
|
93
|
+
|
|
94
|
+
currentIndentLevel -= 1
|
|
95
|
+
|
|
96
|
+
isFirstElement = false
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
str += `${getIndentString()}]`
|
|
100
|
+
|
|
101
|
+
return str
|
|
102
|
+
} else {
|
|
103
|
+
let str = '{'
|
|
104
|
+
let isFirstEntry = true
|
|
105
|
+
|
|
106
|
+
for (const key in obj) {
|
|
107
|
+
let value = obj[key]
|
|
108
|
+
|
|
109
|
+
if (replacerFunc !== undefined) {
|
|
110
|
+
value = replacerFunc(key, value)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (value === undefined) {
|
|
114
|
+
continue
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
currentIndentLevel += 1
|
|
118
|
+
|
|
119
|
+
const saparatingComma = isFirstEntry === true ? '' : ','
|
|
120
|
+
const indentString = getIndentString()
|
|
121
|
+
|
|
122
|
+
const keyDoesNotRequireQuoting = /^[\p{ID_Start}\$_\u200C\u200D](?:[\p{ID_Continue}\$_\u200C\u200D])*$/u.test(key)
|
|
123
|
+
const keyWithPossibleQuotes = keyDoesNotRequireQuoting ? key : `"${escapeStringIfNeeded(key)}"`
|
|
124
|
+
|
|
125
|
+
const spaceAfterColons = space > 0 ? ' ' : ''
|
|
126
|
+
|
|
127
|
+
const encodedValue = encode(value)
|
|
128
|
+
|
|
129
|
+
str += `${saparatingComma}${indentString}${keyWithPossibleQuotes}:${spaceAfterColons}${encodedValue}`
|
|
130
|
+
|
|
131
|
+
currentIndentLevel -= 1
|
|
132
|
+
|
|
133
|
+
isFirstEntry = false
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
str += `${getIndentString()}}`
|
|
137
|
+
|
|
138
|
+
return str
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return encode(obj)
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function escapeStringIfNeeded(str: string): string {
|
|
146
|
+
if (!/["\\\x00-\x1F]/.test(str)) {
|
|
147
|
+
return str
|
|
148
|
+
} else {
|
|
149
|
+
return escapeString(str)
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function escapeString(str: string): string {
|
|
154
|
+
let escapedStr = ''
|
|
155
|
+
|
|
156
|
+
for (let i = 0; i < str.length; i++) {
|
|
157
|
+
const charCode = str.charCodeAt(i)
|
|
158
|
+
|
|
159
|
+
if (charCode >= 32) {
|
|
160
|
+
if (charCode === 34) { // '"'
|
|
161
|
+
escapedStr += '\\"'
|
|
162
|
+
} else if (charCode === 92) { // '\\'
|
|
163
|
+
escapedStr += '\\\\'
|
|
164
|
+
} else {
|
|
165
|
+
escapedStr += String.fromCharCode(charCode)
|
|
166
|
+
}
|
|
167
|
+
} else {
|
|
168
|
+
if (charCode === 10) { // '\n'
|
|
169
|
+
escapedStr += '\\n'
|
|
170
|
+
} else if (charCode === 13) { // '\r'
|
|
171
|
+
escapedStr += '\\r'
|
|
172
|
+
} else if (charCode === 9) { // '\t'
|
|
173
|
+
escapedStr += '\\t'
|
|
174
|
+
} else if (charCode === 12) { // '\f'
|
|
175
|
+
escapedStr += '\\f'
|
|
176
|
+
} else if (charCode === 8) { // '\b'
|
|
177
|
+
escapedStr += '\\b'
|
|
178
|
+
} else if (charCode === 11) { // '\v'
|
|
179
|
+
escapedStr += '\\v'
|
|
180
|
+
} else if (charCode === 0) { // '\v'
|
|
181
|
+
escapedStr += '\\0'
|
|
182
|
+
} else {
|
|
183
|
+
escapedStr += `\\u${charCodeToHex(charCode)}`
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return escapedStr
|
|
189
|
+
}
|