fast-json-stringify 5.6.2 → 5.8.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.
@@ -1,4 +1,4 @@
1
- name: Benchmark
1
+ name: Benchmark PR
2
2
 
3
3
  on:
4
4
  pull_request_target:
@@ -11,15 +11,8 @@ jobs:
11
11
  permissions:
12
12
  contents: read
13
13
  outputs:
14
- PR-BENCH-14: ${{ steps.benchmark-pr.outputs.BENCH_RESULT14 }}
15
- PR-BENCH-16: ${{ steps.benchmark-pr.outputs.BENCH_RESULT16 }}
16
- PR-BENCH-18: ${{ steps.benchmark-pr.outputs.BENCH_RESULT18 }}
17
- MAIN-BENCH-14: ${{ steps.benchmark-main.outputs.BENCH_RESULT14 }}
18
- MAIN-BENCH-16: ${{ steps.benchmark-main.outputs.BENCH_RESULT16 }}
19
- MAIN-BENCH-18: ${{ steps.benchmark-main.outputs.BENCH_RESULT18 }}
20
- strategy:
21
- matrix:
22
- node-version: [14, 16, 18]
14
+ PR-BENCH: ${{ steps.benchmark-pr.outputs.BENCH_RESULT }}
15
+ MASTER-BENCH: ${{ steps.benchmark-master.outputs.BENCH_RESULT }}
23
16
  steps:
24
17
  - uses: actions/checkout@v3
25
18
  with:
@@ -29,7 +22,7 @@ jobs:
29
22
 
30
23
  - uses: actions/setup-node@v3
31
24
  with:
32
- node-version: ${{ matrix.node-version }}
25
+ node-version: 18
33
26
 
34
27
  - name: Install
35
28
  run: |
@@ -38,14 +31,14 @@ jobs:
38
31
  - name: Run benchmark
39
32
  id: benchmark-pr
40
33
  run: |
41
- npm run --silent benchmark > ./bench-result
34
+ npm run --silent bench > ./bench-result
42
35
  content=$(cat ./bench-result)
43
36
  content="${content//'%'/'%25'}"
44
37
  content="${content//$'\n'/'%0A'}"
45
38
  content="${content//$'\r'/'%0D'}"
46
- echo "::set-output name=BENCH_RESULT${{matrix.node-version}}::$content"
39
+ echo "::set-output name=BENCH_RESULT::$content"
47
40
 
48
- # main benchmark
41
+ # master benchmark
49
42
  - uses: actions/checkout@v3
50
43
  with:
51
44
  ref: 'master'
@@ -57,14 +50,15 @@ jobs:
57
50
  - name: Run benchmark
58
51
  id: benchmark-master
59
52
  run: |
60
- npm run --silent benchmark > ./bench-result
53
+ npm run --silent bench > ./bench-result
61
54
  content=$(cat ./bench-result)
62
55
  content="${content//'%'/'%25'}"
63
56
  content="${content//$'\n'/'%0A'}"
64
57
  content="${content//$'\r'/'%0D'}"
65
- echo "::set-output name=BENCH_RESULT${{matrix.node-version}}::$content"
58
+ echo "::set-output name=BENCH_RESULT::$content"
66
59
 
67
60
  output-benchmark:
61
+ if: "always()"
68
62
  needs: [benchmark]
69
63
  runs-on: ubuntu-latest
70
64
  permissions:
@@ -75,38 +69,13 @@ jobs:
75
69
  with:
76
70
  GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
77
71
  message: |
78
- **Node**: 14
79
72
  **PR**:
80
73
  ```
81
- ${{ needs.benchmark.outputs.PR-BENCH-14 }}
74
+ ${{ needs.benchmark.outputs.PR-BENCH }}
82
75
  ```
83
- **MAIN**:
76
+ **MASTER**:
84
77
  ```
85
- ${{ needs.benchmark.outputs.MAIN-BENCH-14 }}
86
- ```
87
-
88
- ---
89
-
90
- **Node**: 16
91
- **PR**:
92
- ```
93
- ${{ needs.benchmark.outputs.PR-BENCH-16 }}
94
- ```
95
- **MAIN**:
96
- ```
97
- ${{ needs.benchmark.outputs.MAIN-BENCH-16 }}
98
- ```
99
-
100
- ---
101
-
102
- **Node**: 18
103
- **PR**:
104
- ```
105
- ${{ needs.benchmark.outputs.PR-BENCH-18 }}
106
- ```
107
- **MAIN**:
108
- ```
109
- ${{ needs.benchmark.outputs.MAIN-BENCH-18 }}
78
+ ${{ needs.benchmark.outputs.MASTER-BENCH }}
110
79
  ```
111
80
 
112
81
  - uses: actions-ecosystem/action-remove-labels@v1
@@ -2,6 +2,11 @@ name: CI
2
2
 
3
3
  on:
4
4
  push:
5
+ branches:
6
+ - main
7
+ - master
8
+ - next
9
+ - 'v*'
5
10
  paths-ignore:
6
11
  - 'docs/**'
7
12
  - '*.md'
package/README.md CHANGED
@@ -689,8 +689,7 @@ console.log(stringify({ firstName: 'Foo', surname: 'bar' })) // '{"firstName":"F
689
689
  ### Standalone Mode
690
690
 
691
691
  The standalone mode is used to compile the code that can be directly run by `node`
692
- itself. You need to install `ajv`, `fast-uri` and `ajv-formats` for
693
- the standalone code to work.
692
+ itself. You need to have `fast-json-stringify` installed for the standalone code to work.
694
693
 
695
694
  ```js
696
695
  const fs = require('fs')
@@ -3,7 +3,7 @@
3
3
  const { workerData: benchmark, parentPort } = require('worker_threads')
4
4
 
5
5
  const Benchmark = require('benchmark')
6
- Benchmark.options.minSamples = 500
6
+ Benchmark.options.minSamples = 100
7
7
 
8
8
  const suite = Benchmark.Suite()
9
9
 
@@ -18,6 +18,11 @@ function getRandomInt (max) {
18
18
  return Math.floor(Math.random() * max)
19
19
  }
20
20
 
21
+ let longSimpleString = ''
22
+ for (let i = 0; i < LONG_STRING_LENGTH; i++) {
23
+ longSimpleString += i
24
+ }
25
+
21
26
  let longString = ''
22
27
  for (let i = 0; i < LONG_STRING_LENGTH; i++) {
23
28
  longString += i
@@ -42,6 +47,20 @@ const benchmarks = [
42
47
  },
43
48
  input: 'hello world'
44
49
  },
50
+ {
51
+ name: 'short string with double quote',
52
+ schema: {
53
+ type: 'string'
54
+ },
55
+ input: 'hello " world'
56
+ },
57
+ {
58
+ name: 'long string without double quotes',
59
+ schema: {
60
+ type: 'string'
61
+ },
62
+ input: longSimpleString
63
+ },
45
64
  {
46
65
  name: 'long string',
47
66
  schema: {
@@ -235,6 +254,28 @@ const benchmarks = [
235
254
  },
236
255
  input: { firstName: 'Max', lastName: 'Power', age: 22 }
237
256
  },
257
+ {
258
+ name: 'simple object with required fields',
259
+ schema: {
260
+ title: 'Example Schema',
261
+ type: 'object',
262
+ properties: {
263
+ firstName: {
264
+ type: 'string'
265
+ },
266
+ lastName: {
267
+ type: ['string', 'null']
268
+ },
269
+ age: {
270
+ description: 'Age in years',
271
+ type: 'integer',
272
+ minimum: 0
273
+ }
274
+ },
275
+ required: ['firstName', 'lastName', 'age']
276
+ },
277
+ input: { firstName: 'Max', lastName: 'Power', age: 22 }
278
+ },
238
279
  {
239
280
  name: 'object with const string property',
240
281
  schema: {
package/index.js CHANGED
@@ -76,6 +76,7 @@ function build (schema, options) {
76
76
  functionsCounter: 0,
77
77
  functionsNamesBySchema: new Map(),
78
78
  options,
79
+ wrapObjects: true,
79
80
  refResolver: new RefResolver(),
80
81
  rootSchemaId: schema.$id || randomUUID(),
81
82
  validatorSchemasIds: new Set()
@@ -123,7 +124,7 @@ function build (schema, options) {
123
124
 
124
125
  // If we have only the invocation of the 'anonymous0' function, we would
125
126
  // basically just wrap the 'anonymous0' function in the 'main' function and
126
- // and the overhead of the intermediate variabe 'json'. We can avoid the
127
+ // and the overhead of the intermediate variable 'json'. We can avoid the
127
128
  // wrapping and the unnecessary memory allocation by aliasing 'anonymous0' to
128
129
  // 'main'
129
130
  if (code === 'json += anonymous0(input)') {
@@ -170,18 +171,15 @@ function build (schema, options) {
170
171
  }
171
172
  }
172
173
 
174
+ /* eslint no-new-func: "off" */
175
+ const contextFunc = new Function('validator', 'serializer', contextFunctionCode)
176
+
173
177
  if (options.mode === 'standalone') {
174
- // lazy load
175
- const isValidatorUsed = context.validatorSchemasIds.size > 0
176
178
  const buildStandaloneCode = require('./lib/standalone')
177
- return buildStandaloneCode(options, validator, isValidatorUsed, contextFunctionCode)
179
+ return buildStandaloneCode(contextFunc, context, serializer, validator)
178
180
  }
179
181
 
180
- /* eslint no-new-func: "off" */
181
- const contextFunc = new Function('validator', 'serializer', contextFunctionCode)
182
- const stringifyFunc = contextFunc(validator, serializer)
183
-
184
- return stringifyFunc
182
+ return contextFunc(validator, serializer)
185
183
  }
186
184
 
187
185
  const objectKeywords = [
@@ -300,59 +298,96 @@ function buildExtraObjectPropertiesSerializer (context, location) {
300
298
  }
301
299
 
302
300
  function buildInnerObject (context, location) {
301
+ let code = ''
303
302
  const schema = location.schema
304
303
  const required = schema.required || []
305
304
 
306
- let code = ''
307
-
308
305
  const propertiesLocation = location.getPropertyLocation('properties')
309
- Object.keys(schema.properties || {}).forEach((key) => {
310
- let propertyLocation = propertiesLocation.getPropertyLocation(key)
311
- if (propertyLocation.schema.$ref) {
312
- propertyLocation = resolveRef(context, location, propertyLocation.schema.$ref)
313
- }
314
306
 
315
- const sanitized = JSON.stringify(key)
307
+ const requiredWithDefault = []
308
+ const requiredWithoutDefault = []
309
+ if (schema.properties) {
310
+ for (const key of Object.keys(schema.properties)) {
311
+ if (required.indexOf(key) === -1) {
312
+ continue
313
+ }
314
+ let propertyLocation = propertiesLocation.getPropertyLocation(key)
315
+ if (propertyLocation.schema.$ref) {
316
+ propertyLocation = resolveRef(context, location, propertyLocation.schema.$ref)
317
+ }
316
318
 
317
- // Using obj['key'] !== undefined instead of obj.hasOwnProperty(prop) for perf reasons,
318
- // see https://github.com/mcollina/fast-json-stringify/pull/3 for discussion.
319
+ const sanitizedKey = JSON.stringify(key)
319
320
 
320
- code += `
321
- if (obj[${sanitized}] !== undefined) {
322
- ${addComma}
323
- json += ${JSON.stringify(sanitized + ':')}
324
- `
321
+ // Using obj['key'] !== undefined instead of obj.hasOwnProperty(prop) for perf reasons,
322
+ // see https://github.com/mcollina/fast-json-stringify/pull/3 for discussion.
323
+ const defaultValue = propertyLocation.schema.default
324
+ if (defaultValue === undefined) {
325
+ code += `if (obj[${sanitizedKey}] === undefined) throw new Error('${sanitizedKey} is required!')\n`
326
+ requiredWithoutDefault.push(key)
327
+ }
328
+ requiredWithDefault.push(key)
329
+ }
330
+ }
325
331
 
326
- code += buildValue(context, propertyLocation, `obj[${sanitized}]`)
332
+ // handle extraneous required fields
333
+ for (const requiredProperty of required) {
334
+ if (requiredWithDefault.indexOf(requiredProperty) !== -1) continue
335
+ code += `if (obj['${requiredProperty}'] === undefined) throw new Error('"${requiredProperty}" is required!')\n`
336
+ }
327
337
 
328
- const defaultValue = propertyLocation.schema.default
329
- if (defaultValue !== undefined) {
330
- code += `
331
- } else {
338
+ code += `
339
+ let addComma = false
340
+ let json = '${context.wrapObjects ? '{' : ''}'
341
+ `
342
+ const wrapObjects = context.wrapObjects
343
+ context.wrapObjects = true
344
+
345
+ if (schema.properties) {
346
+ for (const key of Object.keys(schema.properties)) {
347
+ let propertyLocation = propertiesLocation.getPropertyLocation(key)
348
+ if (propertyLocation.schema.$ref) {
349
+ propertyLocation = resolveRef(context, location, propertyLocation.schema.$ref)
350
+ }
351
+
352
+ const sanitizedKey = JSON.stringify(key)
353
+
354
+ if (requiredWithoutDefault.indexOf(key) !== -1) {
355
+ code += `
332
356
  ${addComma}
333
- json += ${JSON.stringify(sanitized + ':' + JSON.stringify(defaultValue))}
357
+ json += ${JSON.stringify(sanitizedKey + ':')}
358
+ ${buildValue(context, propertyLocation, `obj[${sanitizedKey}]`)}
334
359
  `
335
- } else if (required.includes(key)) {
336
- code += `
337
360
  } else {
338
- throw new Error('${sanitized} is required!')
339
- `
340
- }
341
-
342
- code += `
361
+ // Using obj['key'] !== undefined instead of obj.hasOwnProperty(prop) for perf reasons,
362
+ // see https://github.com/mcollina/fast-json-stringify/pull/3 for discussion.
363
+ code += `
364
+ if (obj[${sanitizedKey}] !== undefined) {
365
+ ${addComma}
366
+ json += ${JSON.stringify(sanitizedKey + ':')}
367
+ ${buildValue(context, propertyLocation, `obj[${sanitizedKey}]`)}
368
+ }
369
+ `
370
+ const defaultValue = propertyLocation.schema.default
371
+ if (defaultValue !== undefined) {
372
+ code += `
373
+ else {
374
+ ${addComma}
375
+ json += ${JSON.stringify(sanitizedKey + ':' + JSON.stringify(defaultValue))}
376
+ }
377
+ `
378
+ }
343
379
  }
344
- `
345
- })
346
-
347
- for (const requiredProperty of required) {
348
- if (schema.properties && schema.properties[requiredProperty] !== undefined) continue
349
- code += `if (obj['${requiredProperty}'] === undefined) throw new Error('"${requiredProperty}" is required!')\n`
380
+ }
350
381
  }
351
382
 
352
383
  if (schema.patternProperties || schema.additionalProperties) {
353
384
  code += buildExtraObjectPropertiesSerializer(context, location)
354
385
  }
355
386
 
387
+ context.wrapObjects = wrapObjects
388
+ code += `
389
+ return json${context.wrapObjects ? ' + \'}\'' : ''}
390
+ `
356
391
  return code
357
392
  }
358
393
 
@@ -507,19 +542,13 @@ function buildObject (context, location) {
507
542
  }
508
543
 
509
544
  let functionCode = `
510
- function ${functionName} (input) {
511
- // ${schemaRef}
512
545
  `
513
546
 
514
547
  functionCode += `
548
+ // ${schemaRef}
549
+ function ${functionName} (input) {
515
550
  const obj = ${toJSON('input')}
516
- let json = '{'
517
- let addComma = false
518
- `
519
-
520
- functionCode += buildInnerObject(context, location)
521
- functionCode += `
522
- return json + '}'
551
+ ${buildInnerObject(context, location)}
523
552
  }
524
553
  `
525
554
 
@@ -571,12 +600,8 @@ function buildArray (context, location) {
571
600
  `
572
601
  }
573
602
 
574
- if (largeArrayMechanism !== 'default') {
575
- if (largeArrayMechanism === 'json-stringify') {
576
- functionCode += `if (arrayLength && arrayLength >= ${largeArraySize}) return JSON.stringify(obj)\n`
577
- } else {
578
- throw new Error(`Unsupported large array mechanism ${largeArrayMechanism}`)
579
- }
603
+ if (largeArrayMechanism === 'json-stringify') {
604
+ functionCode += `if (arrayLength && arrayLength >= ${largeArraySize}) return JSON.stringify(obj)\n`
580
605
  }
581
606
 
582
607
  functionCode += `
@@ -663,8 +688,6 @@ function buildArrayTypeCondition (type, accessor) {
663
688
  return buildArrayTypeCondition(subType, accessor)
664
689
  })
665
690
  condition = `(${conditions.join(' || ')})`
666
- } else {
667
- throw new Error(`${type} unsupported`)
668
691
  }
669
692
  }
670
693
  return condition
@@ -841,9 +864,19 @@ function buildValue (context, location, input) {
841
864
 
842
865
  let code = ''
843
866
 
844
- if (type === undefined && (schema.anyOf || schema.oneOf)) {
867
+ if ((type === undefined || type === 'object') && (schema.anyOf || schema.oneOf)) {
845
868
  context.validatorSchemasIds.add(location.getSchemaId())
846
869
 
870
+ if (schema.type === 'object') {
871
+ context.wrapObjects = false
872
+ const funcName = buildObject(context, location)
873
+ code += `
874
+ json += '{'
875
+ json += ${funcName}(${input})
876
+ json += ','
877
+ `
878
+ }
879
+
847
880
  const type = schema.anyOf ? 'anyOf' : 'oneOf'
848
881
  const anyOfLocation = location.getPropertyLocation(type)
849
882
 
@@ -865,6 +898,12 @@ function buildValue (context, location, input) {
865
898
  code += `
866
899
  else throw new TypeError(\`The value of '${schemaRef}' does not match schema definition.\`)
867
900
  `
901
+ if (schema.type === 'object') {
902
+ code += `
903
+ json += '}'
904
+ `
905
+ context.wrapObjects = true
906
+ }
868
907
  return code
869
908
  }
870
909
 
package/lib/serializer.js CHANGED
@@ -20,6 +20,7 @@ module.exports = class Serializer {
20
20
  this.parseInteger = Math.trunc
21
21
  break
22
22
  }
23
+ this._options = options
23
24
  }
24
25
 
25
26
  asInteger (i) {
@@ -54,7 +55,7 @@ module.exports = class Serializer {
54
55
  if (Number.isNaN(num)) {
55
56
  throw new Error(`The value "${i}" cannot be converted to a number.`)
56
57
  } else if (!Number.isFinite(num)) {
57
- return null
58
+ return 'null'
58
59
  } else {
59
60
  return '' + num
60
61
  }
@@ -112,11 +113,10 @@ module.exports = class Serializer {
112
113
  }
113
114
  }
114
115
 
115
- // Fast escape chars check
116
- if (!STR_ESCAPE.test(str)) {
117
- return '"' + str + '"'
118
- } else if (str.length < 42) {
116
+ if (str.length < 42) {
119
117
  return this.asStringSmall(str)
118
+ } else if (STR_ESCAPE.test(str) === false) {
119
+ return '"' + str + '"'
120
120
  } else {
121
121
  return JSON.stringify(str)
122
122
  }
@@ -129,31 +129,39 @@ module.exports = class Serializer {
129
129
  // 34 and 92 happens all the time, so we
130
130
  // have a fast case for them
131
131
  asStringSmall (str) {
132
- const l = str.length
132
+ const len = str.length
133
133
  let result = ''
134
- let last = 0
135
- let found = false
136
- let surrogateFound = false
134
+ let last = -1
137
135
  let point = 255
136
+
138
137
  // eslint-disable-next-line
139
- for (var i = 0; i < l && point >= 32; i++) {
138
+ for (var i = 0; i < len; i++) {
140
139
  point = str.charCodeAt(i)
140
+ if (point < 32) {
141
+ return JSON.stringify(str)
142
+ }
141
143
  if (point >= 0xD800 && point <= 0xDFFF) {
142
144
  // The current character is a surrogate.
143
- surrogateFound = true
145
+ return JSON.stringify(str)
144
146
  }
145
- if (point === 34 || point === 92) {
147
+ if (
148
+ point === 0x22 || // '"'
149
+ point === 0x5c // '\'
150
+ ) {
151
+ last === -1 && (last = 0)
146
152
  result += str.slice(last, i) + '\\'
147
153
  last = i
148
- found = true
149
154
  }
150
155
  }
151
156
 
152
- if (!found) {
153
- result = str
154
- } else {
155
- result += str.slice(last)
156
- }
157
- return ((point < 32) || (surrogateFound === true)) ? JSON.stringify(str) : '"' + result + '"'
157
+ return (last === -1 && ('"' + str + '"')) || ('"' + result + str.slice(last) + '"')
158
+ }
159
+
160
+ getState () {
161
+ return this._options
162
+ }
163
+
164
+ static restoreFromState (state) {
165
+ return new Serializer(state)
158
166
  }
159
167
  }
package/lib/standalone.js CHANGED
@@ -1,42 +1,31 @@
1
- const fs = require('fs')
2
- const path = require('path')
3
-
4
- function buildStandaloneCode (options, validator, isValidatorUsed, contextFunctionCode) {
5
- const serializerCode = fs.readFileSync(path.join(__dirname, 'serializer.js')).toString()
6
- let buildAjvCode = ''
7
- let ajvSchemasCode = ''
8
-
9
- if (isValidatorUsed) {
10
- let defaultAjvSchema = ''
11
- // we need to export the custom json schema
12
- const defaultMeta = validator.ajv.defaultMeta()
13
- if (typeof defaultMeta === 'string') {
14
- defaultAjvSchema = defaultMeta
15
- } else {
16
- defaultAjvSchema = defaultMeta.$id || defaultMeta.id
17
- }
18
-
19
- ajvSchemasCode += `const validator = new Validator(${JSON.stringify(options.ajv || {})})\n`
20
- for (const [id, schema] of Object.entries(validator.ajv.schemas)) {
21
- // should skip ajv default schema
22
- if (id === defaultAjvSchema) continue
23
- ajvSchemasCode += `validator.ajv.addSchema(${JSON.stringify(schema.schema)}, "${id}")\n`
24
- }
25
- buildAjvCode = fs.readFileSync(path.join(__dirname, 'validator.js')).toString()
26
- buildAjvCode = buildAjvCode.replace("'use strict'", '').replace('module.exports = SchemaValidator', '')
1
+ 'use strict'
2
+
3
+ function buildStandaloneCode (contextFunc, context, serializer, validator) {
4
+ let ajvDependencyCode = ''
5
+ if (context.validatorSchemasIds.size > 0) {
6
+ ajvDependencyCode += `const validatorState = ${JSON.stringify(validator.getState())}\n`
7
+ ajvDependencyCode += 'const validator = Validator.restoreFromState(validatorState)\n'
8
+ } else {
9
+ ajvDependencyCode += 'const validator = null\n'
27
10
  }
28
11
 
29
- const serializerOptions = options && options.rounding ? JSON.stringify({ options: options.rounding }) : ''
30
12
  return `
31
13
  'use strict'
32
- ${serializerCode.replace("'use strict'", '').replace('module.exports = ', '')}
33
- ${buildAjvCode}
34
- const serializer = new Serializer(${serializerOptions})
35
- ${ajvSchemasCode}
14
+ const { dependencies } = require('fast-json-stringify/lib/standalone')
15
+
16
+ const { Serializer, Validator } = dependencies
17
+
18
+ const serializerState = ${JSON.stringify(serializer.getState())}
19
+ const serializer = Serializer.restoreFromState(serializerState)
36
20
 
37
- ${contextFunctionCode.replace('return main', '')}
21
+ ${ajvDependencyCode}
38
22
 
39
- module.exports = main`
23
+ module.exports = ${contextFunc.toString()}(validator, serializer)`
40
24
  }
41
25
 
42
26
  module.exports = buildStandaloneCode
27
+
28
+ module.exports.dependencies = {
29
+ Serializer: require('./serializer'),
30
+ Validator: require('./validator')
31
+ }
package/lib/validator.js CHANGED
@@ -25,6 +25,9 @@ class Validator {
25
25
  return date instanceof Date
26
26
  }
27
27
  })
28
+
29
+ this._ajvSchemas = {}
30
+ this._ajvOptions = ajvOptions || {}
28
31
  }
29
32
 
30
33
  addSchema (schema, schemaName) {
@@ -40,6 +43,7 @@ class Validator {
40
43
  const ajvSchema = clone(schema)
41
44
  this.convertSchemaToAjvFormat(ajvSchema)
42
45
  this.ajv.addSchema(ajvSchema, schemaKey)
46
+ this._ajvSchemas[schemaKey] = schema
43
47
  }
44
48
  }
45
49
 
@@ -70,6 +74,21 @@ class Validator {
70
74
  }
71
75
  }
72
76
  }
77
+
78
+ getState () {
79
+ return {
80
+ ajvOptions: this._ajvOptions,
81
+ ajvSchemas: this._ajvSchemas
82
+ }
83
+ }
84
+
85
+ static restoreFromState (state) {
86
+ const validator = new Validator(state.ajvOptions)
87
+ for (const [id, ajvSchema] of Object.entries(state.ajvSchemas)) {
88
+ validator.ajv.addSchema(ajvSchema, id)
89
+ }
90
+ return validator
91
+ }
73
92
  }
74
93
 
75
94
  module.exports = Validator
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fast-json-stringify",
3
- "version": "5.6.2",
3
+ "version": "5.8.0",
4
4
  "description": "Stringify your JSON at max speed",
5
5
  "main": "index.js",
6
6
  "types": "types/index.d.ts",
@@ -34,7 +34,7 @@
34
34
  "homepage": "https://github.com/fastify/fast-json-stringify#readme",
35
35
  "devDependencies": {
36
36
  "@fastify/pre-commit": "^2.0.2",
37
- "@sinclair/typebox": "^0.25.2",
37
+ "@sinclair/typebox": "^0.29.1",
38
38
  "benchmark": "^2.1.4",
39
39
  "cli-select": "^1.1.2",
40
40
  "compile-json-stringify": "^0.1.2",
@@ -42,8 +42,9 @@
42
42
  "simple-git": "^3.7.1",
43
43
  "standard": "^17.0.0",
44
44
  "tap": "^16.0.1",
45
- "tsd": "^0.25.0",
46
- "webpack": "^5.40.0"
45
+ "tsd": "^0.28.0",
46
+ "webpack": "^5.40.0",
47
+ "fast-json-stringify": "."
47
48
  },
48
49
  "dependencies": {
49
50
  "@fastify/deepmerge": "^1.0.0",
@@ -220,6 +220,75 @@ test('object with nested allOfs', (t) => {
220
220
  t.equal(value, '{"id1":1,"id2":2,"id3":3}')
221
221
  })
222
222
 
223
+ test('object with anyOf nested inside allOf', (t) => {
224
+ t.plan(1)
225
+
226
+ const schema = {
227
+ title: 'object with anyOf nested inside allOf',
228
+ type: 'object',
229
+ allOf: [
230
+ {
231
+ required: ['id1', 'obj'],
232
+ type: 'object',
233
+ properties: {
234
+ id1: {
235
+ type: 'integer'
236
+ },
237
+ obj: {
238
+ type: 'object',
239
+ properties: {
240
+ nested: { type: 'string' }
241
+ }
242
+ }
243
+ }
244
+ },
245
+ {
246
+ anyOf: [
247
+ {
248
+ type: 'object',
249
+ properties: {
250
+ id2: { type: 'string' }
251
+ },
252
+ required: ['id2']
253
+ },
254
+ {
255
+ type: 'object',
256
+ properties: {
257
+ id3: {
258
+ type: 'integer'
259
+ },
260
+ nestedObj: {
261
+ type: 'object',
262
+ properties: {
263
+ nested: { type: 'string' }
264
+ }
265
+ }
266
+ },
267
+ required: ['id3']
268
+ },
269
+ {
270
+ type: 'object',
271
+ properties: {
272
+ id4: { type: 'integer' }
273
+ },
274
+ required: ['id4']
275
+ }
276
+ ]
277
+ }
278
+ ]
279
+ }
280
+
281
+ const stringify = build(schema)
282
+ const value = stringify({
283
+ id1: 1,
284
+ id3: 3,
285
+ id4: 4, // extra prop shouldn't be in result
286
+ obj: { nested: 'yes' },
287
+ nestedObj: { nested: 'yes' }
288
+ })
289
+ t.equal(value, '{"id1":1,"obj":{"nested":"yes"},"id3":3,"nestedObj":{"nested":"yes"}}')
290
+ })
291
+
223
292
  test('object with $ref in allOf', (t) => {
224
293
  t.plan(1)
225
294
 
@@ -344,7 +344,7 @@ test('patternProperties - throw on unknown type', (t) => {
344
344
  }
345
345
  })
346
346
 
347
- test('render a single quote as JSON', (t) => {
347
+ test('render a double quote as JSON /1', (t) => {
348
348
  t.plan(2)
349
349
 
350
350
  const schema = {
@@ -360,6 +360,38 @@ test('render a single quote as JSON', (t) => {
360
360
  t.ok(validate(JSON.parse(output)), 'valid schema')
361
361
  })
362
362
 
363
+ test('render a double quote as JSON /2', (t) => {
364
+ t.plan(2)
365
+
366
+ const schema = {
367
+ type: 'string'
368
+ }
369
+ const toStringify = 'double quote " 2'
370
+
371
+ const validate = validator(schema)
372
+ const stringify = build(schema)
373
+ const output = stringify(toStringify)
374
+
375
+ t.equal(output, JSON.stringify(toStringify))
376
+ t.ok(validate(JSON.parse(output)), 'valid schema')
377
+ })
378
+
379
+ test('render a long string', (t) => {
380
+ t.plan(2)
381
+
382
+ const schema = {
383
+ type: 'string'
384
+ }
385
+ const toStringify = 'the Ultimate Question of Life, the Universe, and Everything.'
386
+
387
+ const validate = validator(schema)
388
+ const stringify = build(schema)
389
+ const output = stringify(toStringify)
390
+
391
+ t.equal(output, JSON.stringify(toStringify))
392
+ t.ok(validate(JSON.parse(output)), 'valid schema')
393
+ })
394
+
363
395
  test('returns JSON.stringify if schema type is boolean', t => {
364
396
  t.plan(1)
365
397
 
@@ -35,6 +35,18 @@ test('render a float as an integer', (t) => {
35
35
  }
36
36
  })
37
37
 
38
+ test('throws on NaN', (t) => {
39
+ t.plan(1)
40
+
41
+ const schema = {
42
+ title: 'integer',
43
+ type: 'integer'
44
+ }
45
+
46
+ const stringify = build(schema)
47
+ t.throws(() => stringify(NaN), new Error('The value "NaN" cannot be converted to an integer.'))
48
+ })
49
+
38
50
  test('render a float as an integer', (t) => {
39
51
  const cases = [
40
52
  { input: Math.PI, output: '3' },
package/test/ref.test.js CHANGED
@@ -2078,3 +2078,42 @@ test('should throw an Error if two non-identical schemas with same id are provid
2078
2078
 
2079
2079
  t.throws(() => build(schema), new Error('There is already another schema with id inner_schema'))
2080
2080
  })
2081
+
2082
+ test('ref internal - throw if schema has definition twice with different shape', (t) => {
2083
+ t.plan(1)
2084
+
2085
+ const schema = {
2086
+ $id: 'test',
2087
+ title: 'object with $ref',
2088
+ definitions: {
2089
+ def: {
2090
+ $id: '#uri',
2091
+ type: 'object',
2092
+ properties: {
2093
+ str: {
2094
+ type: 'string'
2095
+ }
2096
+ },
2097
+ required: ['str']
2098
+ },
2099
+ def2: {
2100
+ $id: '#uri',
2101
+ type: 'object',
2102
+ properties: {
2103
+ num: {
2104
+ type: 'number'
2105
+ }
2106
+ },
2107
+ required: ['num']
2108
+ }
2109
+ },
2110
+ type: 'object',
2111
+ properties: {
2112
+ obj: {
2113
+ $ref: '#uri'
2114
+ }
2115
+ }
2116
+ }
2117
+
2118
+ t.throws(() => build(schema), Error('There is already another schema with id test##uri'))
2119
+ })