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/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.dataPath} ${first.message}`)
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
- var isLong = ${isLong ? isLong.toString() : false}
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 dependencies = [new Ajv(options.ajv)]
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 (isLong && isLong(i)) {
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
- code += `
398
- if (/${regex.replace(/\\*\//g, '\\/')}/.test(keys[i])) {
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
- if (isLong) {
723
- code += `
724
- if (isLong(obj[${sanitized}])) {
725
- ${addComma}
726
- json += ${asString} + ':' + obj[${sanitized}].toString()
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 = ajv.validate(${JSON.stringify(i)}, obj)
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'}(ajv.validate(${JSON.stringify(location.schema)}, ${testValue}))
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'}(ajv.validate(${JSON.stringify(location.schema)}, ${testValue}))
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
- args.push(new Ajv(options.ajv))
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": "2.7.10",
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.11.0",
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"