fast-json-stringify 2.7.10 → 3.0.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/.github/dependabot.yml +4 -0
- package/.github/workflows/benchmark.yml +111 -0
- package/.github/workflows/ci.yml +7 -3
- package/README.md +33 -59
- package/bench.js +44 -0
- package/build-schema-validator.js +14 -9
- package/index.js +101 -62
- package/package.json +3 -4
- package/schema-validator.js +1132 -1748
- package/test/allof.test.js +53 -1
- package/test/bigint.test.js +76 -0
- package/test/debug-mode.test.js +26 -0
- package/test/if-then-else.test.js +16 -1
- package/test/integer.test.js +1 -10
- package/test/nullable.test.js +9 -4
- package/test/recursion.test.js +180 -0
- package/test/side-effect.test.js +39 -0
- package/test/types/tsconfig.json +3 -1
- package/test/typesArray.test.js +2 -2
- package/test/bigint.js +0 -74
- package/test/long.test.js +0 -85
package/index.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
/* eslint no-prototype-builtins: 0 */
|
|
4
4
|
|
|
5
5
|
const Ajv = require('ajv')
|
|
6
|
+
const ajvFormats = require('ajv-formats')
|
|
6
7
|
const merge = require('deepmerge')
|
|
7
8
|
const clone = require('rfdc')({ proto: true })
|
|
8
9
|
const fjsCloned = Symbol('fast-json-stringify.cloned')
|
|
@@ -10,13 +11,6 @@ const fjsCloned = Symbol('fast-json-stringify.cloned')
|
|
|
10
11
|
const validate = require('./schema-validator')
|
|
11
12
|
let stringSimilarity = null
|
|
12
13
|
|
|
13
|
-
let isLong
|
|
14
|
-
try {
|
|
15
|
-
isLong = require('long').isLong
|
|
16
|
-
} catch (e) {
|
|
17
|
-
isLong = null
|
|
18
|
-
}
|
|
19
|
-
|
|
20
14
|
const addComma = `
|
|
21
15
|
if (addComma) {
|
|
22
16
|
json += ','
|
|
@@ -33,7 +27,7 @@ function isValidSchema (schema, name) {
|
|
|
33
27
|
name = ''
|
|
34
28
|
}
|
|
35
29
|
const first = validate.errors[0]
|
|
36
|
-
const err = new Error(`${name}schema is invalid: data${first.
|
|
30
|
+
const err = new Error(`${name}schema is invalid: data${first.instancePath} ${first.message}`)
|
|
37
31
|
err.errors = isValidSchema.errors
|
|
38
32
|
throw err
|
|
39
33
|
}
|
|
@@ -47,7 +41,12 @@ function mergeLocation (source, dest) {
|
|
|
47
41
|
}
|
|
48
42
|
}
|
|
49
43
|
|
|
44
|
+
const arrayItemsReferenceSerializersMap = new Map()
|
|
45
|
+
const objectReferenceSerializersMap = new Map()
|
|
46
|
+
|
|
50
47
|
function build (schema, options) {
|
|
48
|
+
arrayItemsReferenceSerializersMap.clear()
|
|
49
|
+
objectReferenceSerializersMap.clear()
|
|
51
50
|
options = options || {}
|
|
52
51
|
isValidSchema(schema)
|
|
53
52
|
if (options.schema) {
|
|
@@ -74,7 +73,39 @@ function build (schema, options) {
|
|
|
74
73
|
code += `
|
|
75
74
|
${asFunctions}
|
|
76
75
|
|
|
77
|
-
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Used by schemas that are dependant on calling 'ajv.validate' during runtime,
|
|
79
|
+
* it stores the value of the '$id' property of the schema (if it has it) inside
|
|
80
|
+
* a cache which is used to figure out if the schema was compiled into a validator
|
|
81
|
+
* by ajv on a previous call, if it was then the '$id' string will be used to
|
|
82
|
+
* invoke 'ajv.validate', this allows:
|
|
83
|
+
*
|
|
84
|
+
* 1. Schemas that depend on ajv.validate calls to leverage ajv caching system.
|
|
85
|
+
* 2. To avoid errors, since directly invoking 'ajv.validate' with the same
|
|
86
|
+
* schema (that contains an '$id' property) twice will throw an error.
|
|
87
|
+
*/
|
|
88
|
+
const $validateWithAjv = (function() {
|
|
89
|
+
const cache = new Set()
|
|
90
|
+
|
|
91
|
+
return function (schema, target) {
|
|
92
|
+
const id = schema.$id
|
|
93
|
+
|
|
94
|
+
if (!id) {
|
|
95
|
+
return ajv.validate(schema, target)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const cached = cache.has(id)
|
|
99
|
+
|
|
100
|
+
if (cached) {
|
|
101
|
+
return ajv.validate(id, target)
|
|
102
|
+
} else {
|
|
103
|
+
cache.add(id)
|
|
104
|
+
return ajv.validate(schema, target)
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
})()
|
|
108
|
+
|
|
78
109
|
|
|
79
110
|
function parseInteger(int) { return Math.${intParseFunctionName}(int) }
|
|
80
111
|
`
|
|
@@ -133,7 +164,9 @@ function build (schema, options) {
|
|
|
133
164
|
return ${main}
|
|
134
165
|
`
|
|
135
166
|
|
|
136
|
-
const
|
|
167
|
+
const ajvInstance = new Ajv(options.ajv)
|
|
168
|
+
ajvFormats(ajvInstance)
|
|
169
|
+
const dependencies = [ajvInstance]
|
|
137
170
|
const dependenciesName = ['ajv']
|
|
138
171
|
dependenciesName.push(code)
|
|
139
172
|
|
|
@@ -144,6 +177,9 @@ function build (schema, options) {
|
|
|
144
177
|
return dependenciesName
|
|
145
178
|
}
|
|
146
179
|
|
|
180
|
+
arrayItemsReferenceSerializersMap.clear()
|
|
181
|
+
objectReferenceSerializersMap.clear()
|
|
182
|
+
|
|
147
183
|
return (Function.apply(null, dependenciesName).apply(null, dependencies))
|
|
148
184
|
}
|
|
149
185
|
|
|
@@ -234,9 +270,7 @@ function $asNull () {
|
|
|
234
270
|
}
|
|
235
271
|
|
|
236
272
|
function $asInteger (i) {
|
|
237
|
-
if (
|
|
238
|
-
return i.toString()
|
|
239
|
-
} else if (typeof i === 'bigint') {
|
|
273
|
+
if (typeof i === 'bigint') {
|
|
240
274
|
return i.toString()
|
|
241
275
|
} else if (Number.isInteger(i)) {
|
|
242
276
|
return $asNumber(i)
|
|
@@ -394,51 +428,60 @@ function addPatternProperties (location) {
|
|
|
394
428
|
} catch (err) {
|
|
395
429
|
throw new Error(`${err.message}. Found at ${regex} matching ${JSON.stringify(pp[regex])}`)
|
|
396
430
|
}
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
431
|
+
|
|
432
|
+
const ifPpKeyExists = `if (/${regex.replace(/\\*\//g, '\\/')}/.test(keys[i])) {`
|
|
433
|
+
|
|
400
434
|
if (type === 'object') {
|
|
401
435
|
code += `${buildObject(ppLocation, '', 'buildObjectPP' + index)}
|
|
436
|
+
${ifPpKeyExists}
|
|
402
437
|
${addComma}
|
|
403
438
|
json += $asString(keys[i]) + ':' + buildObjectPP${index}(obj[keys[i]])
|
|
404
439
|
`
|
|
405
440
|
} else if (type === 'array') {
|
|
406
441
|
code += `${buildArray(ppLocation, '', 'buildArrayPP' + index)}
|
|
442
|
+
${ifPpKeyExists}
|
|
407
443
|
${addComma}
|
|
408
444
|
json += $asString(keys[i]) + ':' + buildArrayPP${index}(obj[keys[i]])
|
|
409
445
|
`
|
|
410
446
|
} else if (type === 'null') {
|
|
411
447
|
code += `
|
|
448
|
+
${ifPpKeyExists}
|
|
412
449
|
${addComma}
|
|
413
450
|
json += $asString(keys[i]) +':null'
|
|
414
451
|
`
|
|
415
452
|
} else if (type === 'string') {
|
|
416
453
|
code += `
|
|
454
|
+
${ifPpKeyExists}
|
|
417
455
|
${addComma}
|
|
418
456
|
json += $asString(keys[i]) + ':' + ${stringSerializer}(obj[keys[i]])
|
|
419
457
|
`
|
|
420
458
|
} else if (type === 'integer') {
|
|
421
459
|
code += `
|
|
460
|
+
${ifPpKeyExists}
|
|
422
461
|
${addComma}
|
|
423
462
|
json += $asString(keys[i]) + ':' + $asInteger(obj[keys[i]])
|
|
424
463
|
`
|
|
425
464
|
} else if (type === 'number') {
|
|
426
465
|
code += `
|
|
466
|
+
${ifPpKeyExists}
|
|
427
467
|
${addComma}
|
|
428
468
|
json += $asString(keys[i]) + ':' + $asNumber(obj[keys[i]])
|
|
429
469
|
`
|
|
430
470
|
} else if (type === 'boolean') {
|
|
431
471
|
code += `
|
|
472
|
+
${ifPpKeyExists}
|
|
432
473
|
${addComma}
|
|
433
474
|
json += $asString(keys[i]) + ':' + $asBoolean(obj[keys[i]])
|
|
434
475
|
`
|
|
435
476
|
} else if (type === undefined) {
|
|
436
477
|
code += `
|
|
478
|
+
${ifPpKeyExists}
|
|
437
479
|
${addComma}
|
|
438
480
|
json += $asString(keys[i]) + ':' + $asAny(obj[keys[i]])
|
|
439
481
|
`
|
|
440
482
|
} else {
|
|
441
483
|
code += `
|
|
484
|
+
${ifPpKeyExists}
|
|
442
485
|
throw new Error('Cannot coerce ' + obj[keys[i]] + ' to ' + ${JSON.stringify(type)})
|
|
443
486
|
`
|
|
444
487
|
}
|
|
@@ -501,22 +544,11 @@ function additionalProperty (location) {
|
|
|
501
544
|
} else if (type === 'integer') {
|
|
502
545
|
code += `
|
|
503
546
|
var t = Number(obj[keys[i]])
|
|
547
|
+
if (!isNaN(t)) {
|
|
548
|
+
${addComma}
|
|
549
|
+
json += $asString(keys[i]) + ':' + t
|
|
550
|
+
}
|
|
504
551
|
`
|
|
505
|
-
if (isLong) {
|
|
506
|
-
code += `
|
|
507
|
-
if (isLong(obj[keys[i]]) || !isNaN(t)) {
|
|
508
|
-
${addComma}
|
|
509
|
-
json += $asString(keys[i]) + ':' + $asInteger(obj[keys[i]])
|
|
510
|
-
}
|
|
511
|
-
`
|
|
512
|
-
} else {
|
|
513
|
-
code += `
|
|
514
|
-
if (!isNaN(t)) {
|
|
515
|
-
${addComma}
|
|
516
|
-
json += $asString(keys[i]) + ':' + t
|
|
517
|
-
}
|
|
518
|
-
`
|
|
519
|
-
}
|
|
520
552
|
} else if (type === 'number') {
|
|
521
553
|
code += `
|
|
522
554
|
var t = Number(obj[keys[i]])
|
|
@@ -718,33 +750,12 @@ function buildCode (location, code, laterCode, name) {
|
|
|
718
750
|
} else if (type === 'integer') {
|
|
719
751
|
code += `
|
|
720
752
|
var rendered = false
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
rendered = true
|
|
728
|
-
} else {
|
|
729
|
-
var t = Number(obj[${sanitized}])
|
|
730
|
-
if (!isNaN(t)) {
|
|
731
|
-
${addComma}
|
|
732
|
-
json += ${asString} + ':' + t
|
|
733
|
-
rendered = true
|
|
734
|
-
}
|
|
735
|
-
}
|
|
736
|
-
`
|
|
737
|
-
} else {
|
|
738
|
-
code += `
|
|
739
|
-
var t = $asInteger(obj[${sanitized}])
|
|
740
|
-
if (!isNaN(t)) {
|
|
741
|
-
${addComma}
|
|
742
|
-
json += ${asString} + ':' + t
|
|
743
|
-
rendered = true
|
|
744
|
-
}
|
|
745
|
-
`
|
|
746
|
-
}
|
|
747
|
-
code += `
|
|
753
|
+
var t = $asInteger(obj[${sanitized}])
|
|
754
|
+
if (!isNaN(t)) {
|
|
755
|
+
${addComma}
|
|
756
|
+
json += ${asString} + ':' + t
|
|
757
|
+
rendered = true
|
|
758
|
+
}
|
|
748
759
|
if (rendered) {
|
|
749
760
|
`
|
|
750
761
|
} else {
|
|
@@ -802,6 +813,12 @@ function buildCode (location, code, laterCode, name) {
|
|
|
802
813
|
`
|
|
803
814
|
}
|
|
804
815
|
|
|
816
|
+
if (schema.allOf) {
|
|
817
|
+
const builtCode = buildCodeWithAllOfs(location, code, laterCode, name)
|
|
818
|
+
code = builtCode.code
|
|
819
|
+
laterCode = builtCode.laterCode
|
|
820
|
+
}
|
|
821
|
+
|
|
805
822
|
return { code: code, laterCode: laterCode }
|
|
806
823
|
}
|
|
807
824
|
|
|
@@ -858,7 +875,7 @@ function addIfThenElse (location, name) {
|
|
|
858
875
|
let mergedLocation = mergeLocation(location, { schema: merged })
|
|
859
876
|
|
|
860
877
|
code += `
|
|
861
|
-
valid =
|
|
878
|
+
valid = $validateWithAjv(${JSON.stringify(i)}, obj)
|
|
862
879
|
if (valid) {
|
|
863
880
|
`
|
|
864
881
|
if (merged.if && merged.then) {
|
|
@@ -917,6 +934,16 @@ function buildObject (location, code, name) {
|
|
|
917
934
|
}
|
|
918
935
|
`
|
|
919
936
|
}
|
|
937
|
+
|
|
938
|
+
if (objectReferenceSerializersMap.has(schema)) {
|
|
939
|
+
code += `
|
|
940
|
+
return ${objectReferenceSerializersMap.get(schema)}(input)
|
|
941
|
+
}
|
|
942
|
+
`
|
|
943
|
+
return code
|
|
944
|
+
}
|
|
945
|
+
objectReferenceSerializersMap.set(schema, name)
|
|
946
|
+
|
|
920
947
|
code += `
|
|
921
948
|
var obj = ${toJSON('input')}
|
|
922
949
|
var json = '{'
|
|
@@ -969,8 +996,18 @@ function buildArray (location, code, name, key = null) {
|
|
|
969
996
|
schema = location.schema
|
|
970
997
|
schema[fjsCloned] = true
|
|
971
998
|
}
|
|
999
|
+
|
|
972
1000
|
location = refFinder(schema.items.$ref, location)
|
|
973
1001
|
schema.items = location.schema
|
|
1002
|
+
|
|
1003
|
+
if (arrayItemsReferenceSerializersMap.has(schema.items)) {
|
|
1004
|
+
code += `
|
|
1005
|
+
return ${arrayItemsReferenceSerializersMap.get(schema.items)}(obj)
|
|
1006
|
+
}
|
|
1007
|
+
`
|
|
1008
|
+
return code
|
|
1009
|
+
}
|
|
1010
|
+
arrayItemsReferenceSerializersMap.set(schema.items, name)
|
|
974
1011
|
}
|
|
975
1012
|
|
|
976
1013
|
let result = { code: '', laterCode: '' }
|
|
@@ -1188,7 +1225,7 @@ function nested (laterCode, name, key, location, subKey, isArray) {
|
|
|
1188
1225
|
// 2. `nested`, through `buildCode`, replaces any reference in object properties with the actual schema
|
|
1189
1226
|
// (see https://github.com/fastify/fast-json-stringify/blob/6da3b3e8ac24b1ca5578223adedb4083b7adf8db/index.js#L631)
|
|
1190
1227
|
code += `
|
|
1191
|
-
${index === 0 ? 'if' : 'else if'}(
|
|
1228
|
+
${index === 0 ? 'if' : 'else if'}($validateWithAjv(${JSON.stringify(location.schema)}, ${testValue}))
|
|
1192
1229
|
${nestedResult.code}
|
|
1193
1230
|
`
|
|
1194
1231
|
laterCode = nestedResult.laterCode
|
|
@@ -1205,7 +1242,7 @@ function nested (laterCode, name, key, location, subKey, isArray) {
|
|
|
1205
1242
|
const testValue = testSerializer !== undefined ? `${testSerializer}(obj${accessor}, true)` : `obj${accessor}`
|
|
1206
1243
|
// see comment on anyOf about dereferencing the schema before calling ajv.validate
|
|
1207
1244
|
code += `
|
|
1208
|
-
${index === 0 ? 'if' : 'else if'}(
|
|
1245
|
+
${index === 0 ? 'if' : 'else if'}($validateWithAjv(${JSON.stringify(location.schema)}, ${testValue}))
|
|
1209
1246
|
${nestedResult.code}
|
|
1210
1247
|
`
|
|
1211
1248
|
laterCode = nestedResult.laterCode
|
|
@@ -1320,7 +1357,9 @@ module.exports.restore = function (debugModeStr, options = {}) {
|
|
|
1320
1357
|
const args = []
|
|
1321
1358
|
if (debugModeStr.startsWith('ajv')) {
|
|
1322
1359
|
dependencies.unshift('ajv')
|
|
1323
|
-
|
|
1360
|
+
const ajvInstance = new Ajv(options.ajv)
|
|
1361
|
+
ajvFormats(ajvInstance)
|
|
1362
|
+
args.push(ajvInstance)
|
|
1324
1363
|
}
|
|
1325
1364
|
|
|
1326
1365
|
// eslint-disable-next-line
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fast-json-stringify",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0",
|
|
4
4
|
"description": "Stringify your JSON at max speed",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"types": "index.d.ts",
|
|
@@ -30,11 +30,9 @@
|
|
|
30
30
|
},
|
|
31
31
|
"homepage": "https://github.com/fastify/fast-json-stringify#readme",
|
|
32
32
|
"devDependencies": {
|
|
33
|
-
"ajv-pack": "^0.3.1",
|
|
34
33
|
"benchmark": "^2.1.4",
|
|
35
34
|
"compile-json-stringify": "^0.1.2",
|
|
36
35
|
"is-my-json-valid": "^2.20.0",
|
|
37
|
-
"long": "^4.0.0",
|
|
38
36
|
"moment": "^2.24.0",
|
|
39
37
|
"pre-commit": "^1.2.2",
|
|
40
38
|
"proxyquire": "^2.1.3",
|
|
@@ -45,7 +43,8 @@
|
|
|
45
43
|
"webpack": "^5.40.0"
|
|
46
44
|
},
|
|
47
45
|
"dependencies": {
|
|
48
|
-
"ajv": "^6.
|
|
46
|
+
"ajv": "^8.6.2",
|
|
47
|
+
"ajv-formats": "^2.1.1",
|
|
49
48
|
"deepmerge": "^4.2.2",
|
|
50
49
|
"rfdc": "^1.2.0",
|
|
51
50
|
"string-similarity": "^4.0.1"
|