fast-json-stringify 5.5.0 → 5.6.1

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/lib/serializer.js CHANGED
@@ -4,8 +4,8 @@
4
4
  const STR_ESCAPE = /[\u0000-\u001f\u0022\u005c\ud800-\udfff]|[\ud800-\udbff](?![\udc00-\udfff])|(?:[^\ud800-\udbff]|^)[\udc00-\udfff]/
5
5
 
6
6
  module.exports = class Serializer {
7
- constructor (options = {}) {
8
- switch (options.rounding) {
7
+ constructor (options) {
8
+ switch (options && options.rounding) {
9
9
  case 'floor':
10
10
  this.parseInteger = Math.floor
11
11
  break
@@ -15,6 +15,7 @@ module.exports = class Serializer {
15
15
  case 'round':
16
16
  this.parseInteger = Math.round
17
17
  break
18
+ case 'trunc':
18
19
  default:
19
20
  this.parseInteger = Math.trunc
20
21
  break
@@ -22,17 +23,28 @@ module.exports = class Serializer {
22
23
  }
23
24
 
24
25
  asInteger (i) {
25
- if (typeof i === 'bigint') {
26
+ if (typeof i === 'number') {
27
+ if (i === Infinity || i === -Infinity) {
28
+ throw new Error(`The value "${i}" cannot be converted to an integer.`)
29
+ }
30
+ if ((i | 0) === i) {
31
+ return '' + i
32
+ }
33
+ if (Number.isNaN(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'
39
+ } else if (typeof i === 'bigint') {
26
40
  return i.toString()
27
- } else if (Number.isInteger(i)) {
28
- return '' + i
29
41
  } else {
30
42
  /* eslint no-undef: "off" */
31
43
  const integer = this.parseInteger(i)
32
- if (Number.isNaN(integer) || !Number.isFinite(integer)) {
33
- throw new Error(`The value "${i}" cannot be converted to an integer.`)
34
- } else {
44
+ if (Number.isFinite(integer)) {
35
45
  return '' + integer
46
+ } else {
47
+ throw new Error(`The value "${i}" cannot be converted to an integer.`)
36
48
  }
37
49
  }
38
50
  }
@@ -86,23 +98,24 @@ module.exports = class Serializer {
86
98
  }
87
99
 
88
100
  asString (str) {
89
- const quotes = '"'
90
- if (str instanceof Date) {
91
- return quotes + str.toISOString() + quotes
92
- } else if (str === null) {
93
- return quotes + quotes
94
- } else if (str instanceof RegExp) {
95
- str = str.source
96
- } else if (typeof str !== 'string') {
97
- str = str.toString()
101
+ if (typeof str !== 'string') {
102
+ if (str === null) {
103
+ return '""'
104
+ }
105
+ if (str instanceof Date) {
106
+ return '"' + str.toISOString() + '"'
107
+ }
108
+ if (str instanceof RegExp) {
109
+ str = str.source
110
+ } else {
111
+ str = str.toString()
112
+ }
98
113
  }
99
114
 
100
115
  // Fast escape chars check
101
116
  if (!STR_ESCAPE.test(str)) {
102
- return quotes + str + quotes
103
- }
104
-
105
- if (str.length < 42) {
117
+ return '"' + str + '"'
118
+ } else if (str.length < 42) {
106
119
  return this.asStringSmall(str)
107
120
  } else {
108
121
  return JSON.stringify(str)
package/lib/standalone.js CHANGED
@@ -25,19 +25,18 @@ function buildStandaloneCode (options, validator, isValidatorUsed, contextFuncti
25
25
  buildAjvCode = fs.readFileSync(path.join(__dirname, 'validator.js')).toString()
26
26
  buildAjvCode = buildAjvCode.replace("'use strict'", '').replace('module.exports = SchemaValidator', '')
27
27
  }
28
+
29
+ const serializerOptions = options && options.rounding ? JSON.stringify({ options: options.rounding }) : ''
28
30
  return `
29
31
  'use strict'
30
-
31
32
  ${serializerCode.replace("'use strict'", '').replace('module.exports = ', '')}
32
33
  ${buildAjvCode}
33
-
34
- const serializer = new Serializer(${JSON.stringify(options || {})})
34
+ const serializer = new Serializer(${serializerOptions})
35
35
  ${ajvSchemasCode}
36
36
 
37
37
  ${contextFunctionCode.replace('return main', '')}
38
38
 
39
- module.exports = main
40
- `
39
+ module.exports = main`
41
40
  }
42
41
 
43
42
  module.exports = buildStandaloneCode
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fast-json-stringify",
3
- "version": "5.5.0",
3
+ "version": "5.6.1",
4
4
  "description": "Stringify your JSON at max speed",
5
5
  "main": "index.js",
6
6
  "types": "types/index.d.ts",
@@ -12,7 +12,7 @@
12
12
  "lint": "standard",
13
13
  "lint:fix": "standard --fix",
14
14
  "test:typescript": "tsd",
15
- "test:unit": "tap -J test/*.test.js test/**/*.test.js",
15
+ "test:unit": "tap",
16
16
  "test": "npm run test:unit && npm run test:typescript"
17
17
  },
18
18
  "precommit": ["lint", "test"],
@@ -39,13 +39,10 @@
39
39
  "cli-select": "^1.1.2",
40
40
  "compile-json-stringify": "^0.1.2",
41
41
  "is-my-json-valid": "^2.20.0",
42
- "luxon": "^3.0.1",
43
- "proxyquire": "^2.1.3",
44
- "semver": "^7.1.0",
45
42
  "simple-git": "^3.7.1",
46
43
  "standard": "^17.0.0",
47
44
  "tap": "^16.0.1",
48
- "tsd": "^0.24.1",
45
+ "tsd": "^0.25.0",
49
46
  "webpack": "^5.40.0"
50
47
  },
51
48
  "dependencies": {
@@ -1,9 +1,10 @@
1
1
  'use strict'
2
2
 
3
3
  const test = require('tap').test
4
- const { DateTime } = require('luxon')
5
4
  const build = require('..')
6
5
 
6
+ process.env.TZ = 'UTC'
7
+
7
8
  test('allOf: combine type and format ', (t) => {
8
9
  t.plan(1)
9
10
 
@@ -14,9 +15,9 @@ test('allOf: combine type and format ', (t) => {
14
15
  ]
15
16
  }
16
17
  const stringify = build(schema)
17
- const date = new Date()
18
+ const date = new Date(1674263005800)
18
19
  const value = stringify(date)
19
- t.equal(value, `"${DateTime.fromJSDate(date).toFormat('HH:mm:ss')}"`)
20
+ t.equal(value, '"01:03:25"')
20
21
  })
21
22
 
22
23
  test('allOf: combine additional properties ', (t) => {
@@ -482,3 +483,51 @@ test('allof with local anchor reference', (t) => {
482
483
 
483
484
  t.equal(stringify(data), JSON.stringify(data))
484
485
  })
486
+
487
+ test('allOf: throw Error if types mismatch ', (t) => {
488
+ t.plan(1)
489
+
490
+ const schema = {
491
+ allOf: [
492
+ { type: 'string' },
493
+ { type: 'number' }
494
+ ]
495
+ }
496
+ t.throws(() => build(schema), new Error('allOf schemas have different type values'))
497
+ })
498
+
499
+ test('allOf: throw Error if format mismatch ', (t) => {
500
+ t.plan(1)
501
+
502
+ const schema = {
503
+ allOf: [
504
+ { format: 'date' },
505
+ { format: 'time' }
506
+ ]
507
+ }
508
+ t.throws(() => build(schema), new Error('allOf schemas have different format values'))
509
+ })
510
+
511
+ test('allOf: throw Error if nullable mismatch /1', (t) => {
512
+ t.plan(1)
513
+
514
+ const schema = {
515
+ allOf: [
516
+ { nullable: true },
517
+ { nullable: false }
518
+ ]
519
+ }
520
+ t.throws(() => build(schema), new Error('allOf schemas have different nullable values'))
521
+ })
522
+
523
+ test('allOf: throw Error if nullable mismatch /2', (t) => {
524
+ t.plan(1)
525
+
526
+ const schema = {
527
+ allOf: [
528
+ { nullable: false },
529
+ { nullable: true }
530
+ ]
531
+ }
532
+ t.throws(() => build(schema), new Error('allOf schemas have different nullable values'))
533
+ })
package/test/any.test.js CHANGED
@@ -152,3 +152,80 @@ test('empty schema on anyOf', (t) => {
152
152
  t.equal(stringify({ kind: 'Foo', value: true }), '{"kind":"Foo","value":true}')
153
153
  t.equal(stringify({ kind: 'Foo', value: 'hello' }), '{"kind":"Foo","value":"hello"}')
154
154
  })
155
+
156
+ test('should throw a TypeError with the path to the key of the invalid value /1', (t) => {
157
+ t.plan(1)
158
+
159
+ // any on Foo codepath.
160
+ const schema = {
161
+ anyOf: [
162
+ {
163
+ type: 'object',
164
+ properties: {
165
+ kind: {
166
+ type: 'string',
167
+ enum: ['Foo']
168
+ },
169
+ value: {}
170
+ }
171
+ },
172
+ {
173
+ type: 'object',
174
+ properties: {
175
+ kind: {
176
+ type: 'string',
177
+ enum: ['Bar']
178
+ },
179
+ value: {
180
+ type: 'number'
181
+ }
182
+ }
183
+ }
184
+ ]
185
+ }
186
+
187
+ const stringify = build(schema)
188
+
189
+ t.throws(() => stringify({ kind: 'Baz', value: 1 }), new TypeError('The value of \'#\' does not match schema definition.'))
190
+ })
191
+
192
+ test('should throw a TypeError with the path to the key of the invalid value /2', (t) => {
193
+ t.plan(1)
194
+
195
+ // any on Foo codepath.
196
+ const schema = {
197
+ type: 'object',
198
+ properties: {
199
+ data: {
200
+ anyOf: [
201
+ {
202
+ type: 'object',
203
+ properties: {
204
+ kind: {
205
+ type: 'string',
206
+ enum: ['Foo']
207
+ },
208
+ value: {}
209
+ }
210
+ },
211
+ {
212
+ type: 'object',
213
+ properties: {
214
+ kind: {
215
+ type: 'string',
216
+ enum: ['Bar']
217
+ },
218
+ value: {
219
+ type: 'number'
220
+ }
221
+ }
222
+ }
223
+ ]
224
+ }
225
+ }
226
+ }
227
+
228
+ const stringify = build(schema)
229
+
230
+ t.throws(() => stringify({ data: { kind: 'Baz', value: 1 } }), new TypeError('The value of \'#/properties/data\' does not match schema definition.'))
231
+ })
@@ -1,9 +1,10 @@
1
1
  'use strict'
2
2
 
3
- const { DateTime } = require('luxon')
4
3
  const { test } = require('tap')
5
4
  const build = require('..')
6
5
 
6
+ process.env.TZ = 'UTC'
7
+
7
8
  test('object with multiple types field', (t) => {
8
9
  t.plan(2)
9
10
 
@@ -519,10 +520,10 @@ test('anyOf object with nested field date of type string with format or null', (
519
520
  const withOneOfStringify = build(withOneOfSchema)
520
521
 
521
522
  const data = {
522
- prop: { nestedProp: new Date() }
523
+ prop: { nestedProp: new Date(1674263005800) }
523
524
  }
524
525
 
525
- t.equal(withOneOfStringify(data), `{"prop":{"nestedProp":"${DateTime.fromJSDate(data.prop.nestedProp).toISODate()}"}}`)
526
+ t.equal(withOneOfStringify(data), '{"prop":{"nestedProp":"2023-01-21"}}')
526
527
  })
527
528
 
528
529
  test('anyOf object with nested field time of type string with format or null', (t) => {
@@ -547,10 +548,9 @@ test('anyOf object with nested field time of type string with format or null', (
547
548
  const withOneOfStringify = build(withOneOfSchema)
548
549
 
549
550
  const data = {
550
- prop: { nestedProp: new Date() }
551
+ prop: { nestedProp: new Date(1674263005800) }
551
552
  }
552
-
553
- t.equal(withOneOfStringify(data), `{"prop":{"nestedProp":"${DateTime.fromJSDate(data.prop.nestedProp).toFormat('HH:mm:ss')}"}}`)
553
+ t.equal(withOneOfStringify(data), '{"prop":{"nestedProp":"01:03:25"}}')
554
554
  })
555
555
 
556
556
  test('anyOf object with field date of type string with format or null', (t) => {
@@ -3,6 +3,7 @@
3
3
  const test = require('tap').test
4
4
  const validator = require('is-my-json-valid')
5
5
  const build = require('..')
6
+ const Ajv = require('ajv')
6
7
 
7
8
  test('error on invalid largeArrayMechanism', (t) => {
8
9
  t.plan(1)
@@ -265,7 +266,7 @@ test('array items is a list of schema and additionalItems is true, just the desc
265
266
  t.equal(result, '{"foo":["foo"]}')
266
267
  })
267
268
 
268
- test('array items is a list of schema and additionalItems is false', (t) => {
269
+ test('array items is a list of schema and additionalItems is false /1', (t) => {
269
270
  t.plan(1)
270
271
 
271
272
  const schema = {
@@ -274,9 +275,7 @@ test('array items is a list of schema and additionalItems is false', (t) => {
274
275
  foo: {
275
276
  type: 'array',
276
277
  items: [
277
- {
278
- type: 'string'
279
- }
278
+ { type: 'string' }
280
279
  ],
281
280
  additionalItems: false
282
281
  }
@@ -284,18 +283,55 @@ test('array items is a list of schema and additionalItems is false', (t) => {
284
283
  }
285
284
 
286
285
  const stringify = build(schema)
286
+ t.throws(() => stringify({ foo: ['foo', 'bar'] }), new Error('Item at 1 does not match schema definition.'))
287
+ })
287
288
 
288
- try {
289
- stringify({
290
- foo: [
291
- 'foo',
292
- 'bar'
293
- ]
294
- })
295
- t.fail()
296
- } catch (error) {
297
- t.ok(/does not match schema definition./.test(error.message))
289
+ test('array items is a list of schema and additionalItems is false /2', (t) => {
290
+ t.plan(3)
291
+
292
+ const schema = {
293
+ type: 'object',
294
+ properties: {
295
+ foo: {
296
+ type: 'array',
297
+ items: [
298
+ { type: 'string' },
299
+ { type: 'string' }
300
+ ],
301
+ additionalItems: false
302
+ }
303
+ }
304
+ }
305
+
306
+ const stringify = build(schema)
307
+
308
+ t.throws(() => stringify({ foo: [1, 'bar'] }), new Error('Item at 0 does not match schema definition.'))
309
+ t.throws(() => stringify({ foo: ['foo', 1] }), new Error('Item at 1 does not match schema definition.'))
310
+ t.throws(() => stringify({ foo: ['foo', 'bar', 'baz'] }), new Error('Item at 2 does not match schema definition.'))
311
+ })
312
+
313
+ test('array items is a schema and additionalItems is false', (t) => {
314
+ t.plan(2)
315
+
316
+ const schema = {
317
+ type: 'object',
318
+ properties: {
319
+ foo: {
320
+ type: 'array',
321
+ items: { type: 'string' },
322
+ additionalItems: false
323
+ }
324
+ }
298
325
  }
326
+
327
+ const stringify = build(schema)
328
+
329
+ // ajv ignores additionalItems if items is not an Array
330
+ const ajv = new Ajv({ allErrors: true, strict: false })
331
+
332
+ const validate = ajv.compile(schema)
333
+ t.same(stringify({ foo: ['foo', 'bar'] }), '{"foo":["foo","bar"]}')
334
+ t.equal(validate({ foo: ['foo', 'bar'] }), true)
299
335
  })
300
336
 
301
337
  // https://github.com/fastify/fast-json-stringify/issues/279
@@ -493,3 +529,86 @@ buildTest({
493
529
  largeArraySize: 2e4,
494
530
  largeArrayMechanism: 'default'
495
531
  })
532
+
533
+ test('error on invalid value for largeArraySize /1', (t) => {
534
+ t.plan(1)
535
+
536
+ t.throws(() => build({
537
+ title: 'large array of null values with default mechanism',
538
+ type: 'object',
539
+ properties: {
540
+ ids: {
541
+ type: 'array',
542
+ items: { type: 'null' }
543
+ }
544
+ }
545
+ }, {
546
+ largeArraySize: 'invalid'
547
+ }), Error('Unsupported large array size. Expected integer-like, got string with value invalid'))
548
+ })
549
+
550
+ test('error on invalid value for largeArraySize /2', (t) => {
551
+ t.plan(1)
552
+
553
+ t.throws(() => build({
554
+ title: 'large array of null values with default mechanism',
555
+ type: 'object',
556
+ properties: {
557
+ ids: {
558
+ type: 'array',
559
+ items: { type: 'null' }
560
+ }
561
+ }
562
+ }, {
563
+ largeArraySize: Infinity
564
+ }), Error('Unsupported large array size. Expected integer-like, got number with value Infinity'))
565
+ })
566
+
567
+ test('error on invalid value for largeArraySize /3', (t) => {
568
+ t.plan(1)
569
+
570
+ t.throws(() => build({
571
+ title: 'large array of null values with default mechanism',
572
+ type: 'object',
573
+ properties: {
574
+ ids: {
575
+ type: 'array',
576
+ items: { type: 'null' }
577
+ }
578
+ }
579
+ }, {
580
+ largeArraySize: [200]
581
+ }), Error('Unsupported large array size. Expected integer-like, got object with value 200'))
582
+ })
583
+
584
+ buildTest({
585
+ title: 'large array of integers with largeArraySize is bigint',
586
+ type: 'object',
587
+ properties: {
588
+ ids: {
589
+ type: 'array',
590
+ items: { type: 'integer' }
591
+ }
592
+ }
593
+ }, {
594
+ ids: new Array(2e4).fill(42)
595
+ }, {
596
+ largeArraySize: 20000n,
597
+ largeArrayMechanism: 'default'
598
+ })
599
+
600
+ buildTest({
601
+ title: 'large array of integers with largeArraySize is valid string',
602
+ type: 'object',
603
+ properties: {
604
+ ids: {
605
+ type: 'array',
606
+ items: { type: 'integer' }
607
+ }
608
+ }
609
+ }, {
610
+ ids: new Array(1e4).fill(42)
611
+ }, {
612
+ largeArraySize: '10000',
613
+ largeArrayMechanism: 'default'
614
+ })