fast-json-stringify 5.14.0 → 5.15.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/index.js +60 -48
- package/lib/serializer.js +41 -56
- package/package.json +1 -1
- package/test/allof.test.js +36 -0
package/index.js
CHANGED
|
@@ -140,7 +140,19 @@ function build (schema, options) {
|
|
|
140
140
|
const location = new Location(schema, context.rootSchemaId)
|
|
141
141
|
const code = buildValue(context, location, 'input')
|
|
142
142
|
|
|
143
|
-
let contextFunctionCode
|
|
143
|
+
let contextFunctionCode = `
|
|
144
|
+
const JSON_STR_BEGIN_OBJECT = '{'
|
|
145
|
+
const JSON_STR_END_OBJECT = '}'
|
|
146
|
+
const JSON_STR_BEGIN_ARRAY = '['
|
|
147
|
+
const JSON_STR_END_ARRAY = ']'
|
|
148
|
+
const JSON_STR_COMMA = ','
|
|
149
|
+
const JSON_STR_COLONS = ':'
|
|
150
|
+
const JSON_STR_QUOTE = '"'
|
|
151
|
+
const JSON_STR_EMPTY_OBJECT = JSON_STR_BEGIN_OBJECT + JSON_STR_END_OBJECT
|
|
152
|
+
const JSON_STR_EMPTY_ARRAY = JSON_STR_BEGIN_ARRAY + JSON_STR_END_ARRAY
|
|
153
|
+
const JSON_STR_EMPTY_STRING = JSON_STR_QUOTE + JSON_STR_QUOTE
|
|
154
|
+
const JSON_STR_NULL = 'null'
|
|
155
|
+
`
|
|
144
156
|
|
|
145
157
|
// If we have only the invocation of the 'anonymous0' function, we would
|
|
146
158
|
// basically just wrap the 'anonymous0' function in the 'main' function and
|
|
@@ -148,13 +160,13 @@ function build (schema, options) {
|
|
|
148
160
|
// wrapping and the unnecessary memory allocation by aliasing 'anonymous0' to
|
|
149
161
|
// 'main'
|
|
150
162
|
if (code === 'json += anonymous0(input)') {
|
|
151
|
-
contextFunctionCode
|
|
163
|
+
contextFunctionCode += `
|
|
152
164
|
${context.functions.join('\n')}
|
|
153
165
|
const main = anonymous0
|
|
154
166
|
return main
|
|
155
167
|
`
|
|
156
168
|
} else {
|
|
157
|
-
contextFunctionCode
|
|
169
|
+
contextFunctionCode += `
|
|
158
170
|
function main (input) {
|
|
159
171
|
let json = ''
|
|
160
172
|
${code}
|
|
@@ -284,7 +296,7 @@ function buildExtraObjectPropertiesSerializer (context, location, addComma) {
|
|
|
284
296
|
code += `
|
|
285
297
|
if (/${propertyKey.replace(/\\*\//g, '\\/')}/.test(key)) {
|
|
286
298
|
${addComma}
|
|
287
|
-
json += serializer.asString(key) +
|
|
299
|
+
json += serializer.asString(key) + JSON_STR_COLONS
|
|
288
300
|
${buildValue(context, propertyLocation, 'value')}
|
|
289
301
|
continue
|
|
290
302
|
}
|
|
@@ -299,13 +311,13 @@ function buildExtraObjectPropertiesSerializer (context, location, addComma) {
|
|
|
299
311
|
if (additionalPropertiesSchema === true) {
|
|
300
312
|
code += `
|
|
301
313
|
${addComma}
|
|
302
|
-
json += serializer.asString(key) +
|
|
314
|
+
json += serializer.asString(key) + JSON_STR_COLONS + JSON.stringify(value)
|
|
303
315
|
`
|
|
304
316
|
} else {
|
|
305
317
|
const propertyLocation = location.getPropertyLocation('additionalProperties')
|
|
306
318
|
code += `
|
|
307
319
|
${addComma}
|
|
308
|
-
json += serializer.asString(key) +
|
|
320
|
+
json += serializer.asString(key) + JSON_STR_COLONS
|
|
309
321
|
${buildValue(context, propertyLocation, 'value')}
|
|
310
322
|
`
|
|
311
323
|
}
|
|
@@ -333,7 +345,7 @@ function buildInnerObject (context, location) {
|
|
|
333
345
|
)
|
|
334
346
|
const hasRequiredProperties = requiredProperties.includes(propertiesKeys[0])
|
|
335
347
|
|
|
336
|
-
let code = ''
|
|
348
|
+
let code = 'let value\n'
|
|
337
349
|
|
|
338
350
|
for (const key of requiredProperties) {
|
|
339
351
|
if (!propertiesKeys.includes(key)) {
|
|
@@ -341,12 +353,12 @@ function buildInnerObject (context, location) {
|
|
|
341
353
|
}
|
|
342
354
|
}
|
|
343
355
|
|
|
344
|
-
code += 'let json = \
|
|
356
|
+
code += 'let json = JSON_STR_BEGIN_OBJECT\n'
|
|
345
357
|
|
|
346
358
|
let addComma = ''
|
|
347
359
|
if (!hasRequiredProperties) {
|
|
348
360
|
code += 'let addComma = false\n'
|
|
349
|
-
addComma = '!addComma && (addComma = true) || (json +=
|
|
361
|
+
addComma = '!addComma && (addComma = true) || (json += JSON_STR_COMMA)'
|
|
350
362
|
}
|
|
351
363
|
|
|
352
364
|
for (const key of propertiesKeys) {
|
|
@@ -360,10 +372,11 @@ function buildInnerObject (context, location) {
|
|
|
360
372
|
const isRequired = requiredProperties.includes(key)
|
|
361
373
|
|
|
362
374
|
code += `
|
|
363
|
-
|
|
375
|
+
value = obj[${sanitizedKey}]
|
|
376
|
+
if (value !== undefined) {
|
|
364
377
|
${addComma}
|
|
365
378
|
json += ${JSON.stringify(sanitizedKey + ':')}
|
|
366
|
-
${buildValue(context, propertyLocation,
|
|
379
|
+
${buildValue(context, propertyLocation, 'value')}
|
|
367
380
|
}`
|
|
368
381
|
|
|
369
382
|
if (defaultValue !== undefined) {
|
|
@@ -391,7 +404,7 @@ function buildInnerObject (context, location) {
|
|
|
391
404
|
}
|
|
392
405
|
|
|
393
406
|
code += `
|
|
394
|
-
return json +
|
|
407
|
+
return json + JSON_STR_END_OBJECT
|
|
395
408
|
`
|
|
396
409
|
return code
|
|
397
410
|
}
|
|
@@ -438,7 +451,7 @@ function cloneOriginSchema (context, schema, schemaId) {
|
|
|
438
451
|
for (const key in schema) {
|
|
439
452
|
let value = schema[key]
|
|
440
453
|
|
|
441
|
-
if (key === '$ref' && value.charAt(0) === '#') {
|
|
454
|
+
if (key === '$ref' && typeof value === 'string' && value.charAt(0) === '#') {
|
|
442
455
|
value = schemaId + value
|
|
443
456
|
}
|
|
444
457
|
|
|
@@ -482,7 +495,7 @@ function buildObject (context, location) {
|
|
|
482
495
|
// ${schemaRef}
|
|
483
496
|
function ${functionName} (input) {
|
|
484
497
|
const obj = ${toJSON('input')}
|
|
485
|
-
${!nullable ? 'if (obj === null) return
|
|
498
|
+
${!nullable ? 'if (obj === null) return JSON_STR_EMPTY_OBJECT' : ''}
|
|
486
499
|
|
|
487
500
|
${buildInnerObject(context, location)}
|
|
488
501
|
}
|
|
@@ -523,7 +536,7 @@ function buildArray (context, location) {
|
|
|
523
536
|
|
|
524
537
|
const nullable = schema.nullable === true
|
|
525
538
|
functionCode += `
|
|
526
|
-
${!nullable ? 'if (obj === null) return
|
|
539
|
+
${!nullable ? 'if (obj === null) return JSON_STR_EMPTY_ARRAY' : ''}
|
|
527
540
|
if (!Array.isArray(obj)) {
|
|
528
541
|
throw new TypeError(\`The value of '${schemaRef}' does not match schema definition.\`)
|
|
529
542
|
}
|
|
@@ -539,25 +552,26 @@ function buildArray (context, location) {
|
|
|
539
552
|
}
|
|
540
553
|
|
|
541
554
|
if (largeArrayMechanism === 'json-stringify') {
|
|
542
|
-
functionCode += `if (arrayLength
|
|
555
|
+
functionCode += `if (arrayLength >= ${largeArraySize}) return JSON.stringify(obj)\n`
|
|
543
556
|
}
|
|
544
557
|
|
|
545
558
|
functionCode += `
|
|
546
|
-
|
|
559
|
+
const arrayEnd = arrayLength - 1
|
|
560
|
+
let value
|
|
561
|
+
let json = ''
|
|
547
562
|
`
|
|
548
563
|
|
|
549
564
|
if (Array.isArray(itemsSchema)) {
|
|
550
565
|
for (let i = 0; i < itemsSchema.length; i++) {
|
|
551
566
|
const item = itemsSchema[i]
|
|
552
|
-
|
|
567
|
+
functionCode += `value = obj[${i}]`
|
|
568
|
+
const tmpRes = buildValue(context, itemsLocation.getPropertyLocation(i), 'value')
|
|
553
569
|
functionCode += `
|
|
554
570
|
if (${i} < arrayLength) {
|
|
555
571
|
if (${buildArrayTypeCondition(item.type, `[${i}]`)}) {
|
|
556
|
-
let json = ''
|
|
557
572
|
${tmpRes}
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
jsonOutput += ','
|
|
573
|
+
if (${i} < arrayEnd) {
|
|
574
|
+
json += JSON_STR_COMMA
|
|
561
575
|
}
|
|
562
576
|
} else {
|
|
563
577
|
throw new Error(\`Item at ${i} does not match schema definition.\`)
|
|
@@ -569,9 +583,9 @@ function buildArray (context, location) {
|
|
|
569
583
|
if (schema.additionalItems) {
|
|
570
584
|
functionCode += `
|
|
571
585
|
for (let i = ${itemsSchema.length}; i < arrayLength; i++) {
|
|
572
|
-
|
|
573
|
-
if (i <
|
|
574
|
-
|
|
586
|
+
json += JSON.stringify(obj[i])
|
|
587
|
+
if (i < arrayEnd) {
|
|
588
|
+
json += JSON_STR_COMMA
|
|
575
589
|
}
|
|
576
590
|
}`
|
|
577
591
|
}
|
|
@@ -579,17 +593,15 @@ function buildArray (context, location) {
|
|
|
579
593
|
const code = buildValue(context, itemsLocation, 'obj[i]')
|
|
580
594
|
functionCode += `
|
|
581
595
|
for (let i = 0; i < arrayLength; i++) {
|
|
582
|
-
let json = ''
|
|
583
596
|
${code}
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
jsonOutput += ','
|
|
597
|
+
if (i < arrayEnd) {
|
|
598
|
+
json += JSON_STR_COMMA
|
|
587
599
|
}
|
|
588
600
|
}`
|
|
589
601
|
}
|
|
590
602
|
|
|
591
603
|
functionCode += `
|
|
592
|
-
return
|
|
604
|
+
return JSON_STR_BEGIN_ARRAY + json + JSON_STR_END_ARRAY
|
|
593
605
|
}`
|
|
594
606
|
|
|
595
607
|
context.functions.push(functionCode)
|
|
@@ -600,33 +612,33 @@ function buildArrayTypeCondition (type, accessor) {
|
|
|
600
612
|
let condition
|
|
601
613
|
switch (type) {
|
|
602
614
|
case 'null':
|
|
603
|
-
condition =
|
|
615
|
+
condition = 'value === null'
|
|
604
616
|
break
|
|
605
617
|
case 'string':
|
|
606
|
-
condition = `typeof
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
618
|
+
condition = `typeof value === 'string' ||
|
|
619
|
+
value === null ||
|
|
620
|
+
value instanceof Date ||
|
|
621
|
+
value instanceof RegExp ||
|
|
610
622
|
(
|
|
611
|
-
typeof
|
|
612
|
-
typeof
|
|
613
|
-
|
|
623
|
+
typeof value === "object" &&
|
|
624
|
+
typeof value.toString === "function" &&
|
|
625
|
+
value.toString !== Object.prototype.toString
|
|
614
626
|
)`
|
|
615
627
|
break
|
|
616
628
|
case 'integer':
|
|
617
|
-
condition =
|
|
629
|
+
condition = 'Number.isInteger(value)'
|
|
618
630
|
break
|
|
619
631
|
case 'number':
|
|
620
|
-
condition =
|
|
632
|
+
condition = 'Number.isFinite(value)'
|
|
621
633
|
break
|
|
622
634
|
case 'boolean':
|
|
623
|
-
condition =
|
|
635
|
+
condition = 'typeof value === \'boolean\''
|
|
624
636
|
break
|
|
625
637
|
case 'object':
|
|
626
|
-
condition =
|
|
638
|
+
condition = 'value && typeof value === \'object\' && value.constructor === Object'
|
|
627
639
|
break
|
|
628
640
|
case 'array':
|
|
629
|
-
condition =
|
|
641
|
+
condition = 'Array.isArray(value)'
|
|
630
642
|
break
|
|
631
643
|
default:
|
|
632
644
|
if (Array.isArray(type)) {
|
|
@@ -717,7 +729,7 @@ function buildSingleTypeSerializer (context, location, input) {
|
|
|
717
729
|
|
|
718
730
|
switch (schema.type) {
|
|
719
731
|
case 'null':
|
|
720
|
-
return 'json +=
|
|
732
|
+
return 'json += JSON_STR_NULL'
|
|
721
733
|
case 'string': {
|
|
722
734
|
if (schema.format === 'date-time') {
|
|
723
735
|
return `json += serializer.asDateTime(${input})`
|
|
@@ -731,9 +743,9 @@ function buildSingleTypeSerializer (context, location, input) {
|
|
|
731
743
|
return `
|
|
732
744
|
if (typeof ${input} !== 'string') {
|
|
733
745
|
if (${input} === null) {
|
|
734
|
-
json +=
|
|
746
|
+
json += JSON_STR_EMPTY_STRING
|
|
735
747
|
} else if (${input} instanceof Date) {
|
|
736
|
-
json +=
|
|
748
|
+
json += JSON_STR_QUOTE + ${input}.toISOString() + JSON_STR_QUOTE
|
|
737
749
|
} else if (${input} instanceof RegExp) {
|
|
738
750
|
json += serializer.asString(${input}.source)
|
|
739
751
|
} else {
|
|
@@ -777,7 +789,7 @@ function buildConstSerializer (location, input) {
|
|
|
777
789
|
if (hasNullType) {
|
|
778
790
|
code += `
|
|
779
791
|
if (${input} === null) {
|
|
780
|
-
json +=
|
|
792
|
+
json += JSON_STR_NULL
|
|
781
793
|
} else {
|
|
782
794
|
`
|
|
783
795
|
}
|
|
@@ -984,7 +996,7 @@ function buildValue (context, location, input) {
|
|
|
984
996
|
if (nullable) {
|
|
985
997
|
code += `
|
|
986
998
|
if (${input} === null) {
|
|
987
|
-
json +=
|
|
999
|
+
json += JSON_STR_NULL
|
|
988
1000
|
} else {
|
|
989
1001
|
`
|
|
990
1002
|
}
|
package/lib/serializer.js
CHANGED
|
@@ -24,38 +24,29 @@ module.exports = class Serializer {
|
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
asInteger (i) {
|
|
27
|
-
if (
|
|
28
|
-
|
|
29
|
-
return '' + i
|
|
30
|
-
}
|
|
31
|
-
// check if number is Infinity or NaN
|
|
32
|
-
// eslint-disable-next-line no-self-compare
|
|
33
|
-
if (i === Infinity || i === -Infinity || i !== i) {
|
|
34
|
-
throw new Error(`The value "${i}" cannot be converted to an integer.`)
|
|
35
|
-
}
|
|
36
|
-
return this.parseInteger(i)
|
|
37
|
-
} else if (i === null) {
|
|
38
|
-
return '0'
|
|
27
|
+
if (Number.isInteger(i)) {
|
|
28
|
+
return '' + i
|
|
39
29
|
} else if (typeof i === 'bigint') {
|
|
40
30
|
return i.toString()
|
|
41
|
-
} else {
|
|
42
|
-
/* eslint no-undef: "off" */
|
|
43
|
-
const integer = this.parseInteger(i)
|
|
44
|
-
if (Number.isFinite(integer)) {
|
|
45
|
-
return '' + integer
|
|
46
|
-
} else {
|
|
47
|
-
throw new Error(`The value "${i}" cannot be converted to an integer.`)
|
|
48
|
-
}
|
|
49
31
|
}
|
|
32
|
+
/* eslint no-undef: "off" */
|
|
33
|
+
const integer = this.parseInteger(i)
|
|
34
|
+
// check if number is Infinity or NaN
|
|
35
|
+
// eslint-disable-next-line no-self-compare
|
|
36
|
+
if (integer === Infinity || integer === -Infinity || integer !== integer) {
|
|
37
|
+
throw new Error(`The value "${i}" cannot be converted to an integer.`)
|
|
38
|
+
}
|
|
39
|
+
return '' + integer
|
|
50
40
|
}
|
|
51
41
|
|
|
52
42
|
asNumber (i) {
|
|
53
|
-
|
|
43
|
+
// fast cast to number
|
|
44
|
+
const num = +i
|
|
54
45
|
// check if number is NaN
|
|
55
46
|
// eslint-disable-next-line no-self-compare
|
|
56
47
|
if (num !== num) {
|
|
57
48
|
throw new Error(`The value "${i}" cannot be converted to a number.`)
|
|
58
|
-
} else if (
|
|
49
|
+
} else if (num === Infinity || num === -Infinity) {
|
|
59
50
|
return 'null'
|
|
60
51
|
} else {
|
|
61
52
|
return '' + num
|
|
@@ -100,9 +91,34 @@ module.exports = class Serializer {
|
|
|
100
91
|
}
|
|
101
92
|
|
|
102
93
|
asString (str) {
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
94
|
+
const len = str.length
|
|
95
|
+
if (len < 42) {
|
|
96
|
+
// magically escape strings for json
|
|
97
|
+
// relying on their charCodeAt
|
|
98
|
+
// everything below 32 needs JSON.stringify()
|
|
99
|
+
// every string that contain surrogate needs JSON.stringify()
|
|
100
|
+
// 34 and 92 happens all the time, so we
|
|
101
|
+
// have a fast case for them
|
|
102
|
+
let result = ''
|
|
103
|
+
let last = -1
|
|
104
|
+
let point = 255
|
|
105
|
+
// eslint-disable-next-line
|
|
106
|
+
for (var i = 0; i < len; i++) {
|
|
107
|
+
point = str.charCodeAt(i)
|
|
108
|
+
if (
|
|
109
|
+
point === 0x22 || // '"'
|
|
110
|
+
point === 0x5c // '\'
|
|
111
|
+
) {
|
|
112
|
+
last === -1 && (last = 0)
|
|
113
|
+
result += str.slice(last, i) + '\\'
|
|
114
|
+
last = i
|
|
115
|
+
} else if (point < 32 || (point >= 0xD800 && point <= 0xDFFF)) {
|
|
116
|
+
// The current character is non-printable characters or a surrogate.
|
|
117
|
+
return JSON.stringify(str)
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return (last === -1 && ('"' + str + '"')) || ('"' + result + str.slice(last) + '"')
|
|
121
|
+
} else if (len < 5000 && STR_ESCAPE.test(str) === false) {
|
|
106
122
|
// Only use the regular expression for shorter input. The overhead is otherwise too much.
|
|
107
123
|
return '"' + str + '"'
|
|
108
124
|
} else {
|
|
@@ -114,37 +130,6 @@ module.exports = class Serializer {
|
|
|
114
130
|
return '"' + str + '"'
|
|
115
131
|
}
|
|
116
132
|
|
|
117
|
-
// magically escape strings for json
|
|
118
|
-
// relying on their charCodeAt
|
|
119
|
-
// everything below 32 needs JSON.stringify()
|
|
120
|
-
// every string that contain surrogate needs JSON.stringify()
|
|
121
|
-
// 34 and 92 happens all the time, so we
|
|
122
|
-
// have a fast case for them
|
|
123
|
-
asStringSmall (str) {
|
|
124
|
-
const len = str.length
|
|
125
|
-
let result = ''
|
|
126
|
-
let last = -1
|
|
127
|
-
let point = 255
|
|
128
|
-
|
|
129
|
-
// eslint-disable-next-line
|
|
130
|
-
for (var i = 0; i < len; i++) {
|
|
131
|
-
point = str.charCodeAt(i)
|
|
132
|
-
if (
|
|
133
|
-
point === 0x22 || // '"'
|
|
134
|
-
point === 0x5c // '\'
|
|
135
|
-
) {
|
|
136
|
-
last === -1 && (last = 0)
|
|
137
|
-
result += str.slice(last, i) + '\\'
|
|
138
|
-
last = i
|
|
139
|
-
} else if (point < 32 || (point >= 0xD800 && point <= 0xDFFF)) {
|
|
140
|
-
// The current character is non-printable characters or a surrogate.
|
|
141
|
-
return JSON.stringify(str)
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
return (last === -1 && ('"' + str + '"')) || ('"' + result + str.slice(last) + '"')
|
|
146
|
-
}
|
|
147
|
-
|
|
148
133
|
getState () {
|
|
149
134
|
return this._options
|
|
150
135
|
}
|
package/package.json
CHANGED
package/test/allof.test.js
CHANGED
|
@@ -717,3 +717,39 @@ test('external recursive allOfs', (t) => {
|
|
|
717
717
|
const stringify = build(schema, { schema: { externalSchema } })
|
|
718
718
|
t.equal(stringify(data), '{"a":{"bar":"42","foo":{}},"b":{"bar":"42","foo":{}}}')
|
|
719
719
|
})
|
|
720
|
+
|
|
721
|
+
test('do not crash with $ref prop', (t) => {
|
|
722
|
+
t.plan(1)
|
|
723
|
+
|
|
724
|
+
const schema = {
|
|
725
|
+
title: 'object with $ref',
|
|
726
|
+
type: 'object',
|
|
727
|
+
properties: {
|
|
728
|
+
outside: {
|
|
729
|
+
$ref: '#/$defs/outside'
|
|
730
|
+
}
|
|
731
|
+
},
|
|
732
|
+
$defs: {
|
|
733
|
+
inside: {
|
|
734
|
+
type: 'object',
|
|
735
|
+
properties: {
|
|
736
|
+
$ref: {
|
|
737
|
+
type: 'string'
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
},
|
|
741
|
+
outside: {
|
|
742
|
+
allOf: [{
|
|
743
|
+
$ref: '#/$defs/inside'
|
|
744
|
+
}]
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
const stringify = build(schema)
|
|
749
|
+
const value = stringify({
|
|
750
|
+
outside: {
|
|
751
|
+
$ref: 'true'
|
|
752
|
+
}
|
|
753
|
+
})
|
|
754
|
+
t.equal(value, '{"outside":{"$ref":"true"}}')
|
|
755
|
+
})
|