fast-json-stringify 3.0.3 → 4.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/package.json CHANGED
@@ -1,11 +1,14 @@
1
1
  {
2
2
  "name": "fast-json-stringify",
3
- "version": "3.0.3",
3
+ "version": "4.0.0",
4
4
  "description": "Stringify your JSON at max speed",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
7
7
  "scripts": {
8
- "benchmark": "node bench.js",
8
+ "bench": "node ./benchmark/bench.js",
9
+ "bench:cmp": "node ./benchmark/bench-cmp-branch.js",
10
+ "bench:cmp:ci": "node ./benchmark/bench-cmp-branch.js --ci",
11
+ "benchmark": "node ./benchmark/bench-cmp-lib.js",
9
12
  "lint:fix": "standard --fix",
10
13
  "test:lint": "standard",
11
14
  "test:typescript": "tsc --project ./test/types/tsconfig.json",
@@ -32,21 +35,25 @@
32
35
  "devDependencies": {
33
36
  "@sinclair/typebox": "^0.23.3",
34
37
  "benchmark": "^2.1.4",
38
+ "chalk": "^4.1.2",
35
39
  "compile-json-stringify": "^0.1.2",
40
+ "inquirer": "^8.2.4",
36
41
  "is-my-json-valid": "^2.20.0",
37
- "moment": "^2.24.0",
42
+ "luxon": "^2.4.0",
38
43
  "pre-commit": "^1.2.2",
39
44
  "proxyquire": "^2.1.3",
40
45
  "semver": "^7.1.0",
41
- "standard": "^16.0.1",
42
- "tap": "^15.0.0",
46
+ "simple-git": "^3.7.1",
47
+ "standard": "^17.0.0",
48
+ "tap": "^16.0.1",
43
49
  "typescript": "^4.0.2",
44
50
  "webpack": "^5.40.0"
45
51
  },
46
52
  "dependencies": {
47
- "ajv": "^8.6.2",
53
+ "ajv": "^8.10.0",
48
54
  "ajv-formats": "^2.1.1",
49
55
  "deepmerge": "^4.2.2",
56
+ "fast-uri": "^2.0.0",
50
57
  "rfdc": "^1.2.0",
51
58
  "string-similarity": "^4.0.1"
52
59
  },
@@ -19,7 +19,7 @@ test('additionalProperties', (t) => {
19
19
  })
20
20
 
21
21
  const obj = { str: 'test', foo: 42, ofoo: true, foof: 'string', objfoo: { a: true } }
22
- t.equal('{"str":"test","foo":"42","ofoo":"true","foof":"string","objfoo":"[object Object]"}', stringify(obj))
22
+ t.equal(stringify(obj), '{"str":"test","foo":"42","ofoo":"true","foof":"string","objfoo":"[object Object]"}')
23
23
  })
24
24
 
25
25
  test('additionalProperties should not change properties', (t) => {
@@ -38,7 +38,7 @@ test('additionalProperties should not change properties', (t) => {
38
38
  })
39
39
 
40
40
  const obj = { foo: '42', ofoo: 42 }
41
- t.equal('{"foo":"42","ofoo":42}', stringify(obj))
41
+ t.equal(stringify(obj), '{"foo":"42","ofoo":42}')
42
42
  })
43
43
 
44
44
  test('additionalProperties should not change properties and patternProperties', (t) => {
@@ -62,7 +62,7 @@ test('additionalProperties should not change properties and patternProperties',
62
62
  })
63
63
 
64
64
  const obj = { foo: '42', ofoo: 42, test: '42' }
65
- t.equal('{"foo":"42","ofoo":"42","test":42}', stringify(obj))
65
+ t.equal(stringify(obj), '{"foo":"42","ofoo":"42","test":42}')
66
66
  })
67
67
 
68
68
  test('additionalProperties set to true, use of fast-safe-stringify', (t) => {
@@ -75,7 +75,7 @@ test('additionalProperties set to true, use of fast-safe-stringify', (t) => {
75
75
  })
76
76
 
77
77
  const obj = { foo: true, ofoo: 42, arrfoo: ['array', 'test'], objfoo: { a: 'world' } }
78
- t.equal('{"foo":true,"ofoo":42,"arrfoo":["array","test"],"objfoo":{"a":"world"}}', stringify(obj))
78
+ t.equal(stringify(obj), '{"foo":true,"ofoo":42,"arrfoo":["array","test"],"objfoo":{"a":"world"}}')
79
79
  })
80
80
 
81
81
  test('additionalProperties - string coerce', (t) => {
@@ -90,7 +90,7 @@ test('additionalProperties - string coerce', (t) => {
90
90
  })
91
91
 
92
92
  const obj = { foo: true, ofoo: 42, arrfoo: ['array', 'test'], objfoo: { a: 'world' } }
93
- t.equal('{"foo":"true","ofoo":"42","arrfoo":"array,test","objfoo":"[object Object]"}', stringify(obj))
93
+ t.equal(stringify(obj), '{"foo":"true","ofoo":"42","arrfoo":"array,test","objfoo":"[object Object]"}')
94
94
  })
95
95
 
96
96
  test('additionalProperties - number skip', (t) => {
@@ -104,7 +104,8 @@ test('additionalProperties - number skip', (t) => {
104
104
  }
105
105
  })
106
106
 
107
- const obj = { foo: true, ofoo: '42', xfoo: 'string', arrfoo: [1, 2], objfoo: { num: 42 } }
107
+ // const obj = { foo: true, ofoo: '42', xfoo: 'string', arrfoo: [1, 2], objfoo: { num: 42 } }
108
+ const obj = { foo: true, ofoo: '42' }
108
109
  t.equal(stringify(obj), '{"foo":1,"ofoo":42}')
109
110
  })
110
111
 
@@ -140,11 +141,11 @@ test('additionalProperties - object coerce', (t) => {
140
141
  })
141
142
 
142
143
  const obj = { objfoo: { answer: 42 } }
143
- t.equal('{"objfoo":{"answer":42}}', stringify(obj))
144
+ t.equal(stringify(obj), '{"objfoo":{"answer":42}}')
144
145
  })
145
146
 
146
147
  test('additionalProperties - array coerce', (t) => {
147
- t.plan(1)
148
+ t.plan(2)
148
149
  const stringify = build({
149
150
  title: 'check array coerce',
150
151
  type: 'object',
@@ -157,8 +158,11 @@ test('additionalProperties - array coerce', (t) => {
157
158
  }
158
159
  })
159
160
 
160
- const obj = { foo: 'true', ofoo: 0, arrfoo: [1, 2], objfoo: { tyrion: 'lannister' } }
161
- t.equal('{"foo":["t","r","u","e"],"ofoo":[],"arrfoo":["1","2"],"objfoo":[]}', stringify(obj))
161
+ const coercibleValues = { arrfoo: [1, 2] }
162
+ t.equal(stringify(coercibleValues), '{"arrfoo":["1","2"]}')
163
+
164
+ const incoercibleValues = { foo: 'true', ofoo: 0, objfoo: { tyrion: 'lannister' } }
165
+ t.throws(() => stringify(incoercibleValues))
162
166
  })
163
167
 
164
168
  test('additionalProperties with empty schema', (t) => {
@@ -169,7 +173,7 @@ test('additionalProperties with empty schema', (t) => {
169
173
  })
170
174
 
171
175
  const obj = { a: 1, b: true, c: null }
172
- t.equal('{"a":1,"b":true,"c":null}', stringify(obj))
176
+ t.equal(stringify(obj), '{"a":1,"b":true,"c":null}')
173
177
  })
174
178
 
175
179
  test('additionalProperties with nested empty schema', (t) => {
@@ -183,7 +187,7 @@ test('additionalProperties with nested empty schema', (t) => {
183
187
  })
184
188
 
185
189
  const obj = { data: { a: 1, b: true, c: null } }
186
- t.equal('{"data":{"a":1,"b":true,"c":null}}', stringify(obj))
190
+ t.equal(stringify(obj), '{"data":{"a":1,"b":true,"c":null}}')
187
191
  })
188
192
 
189
193
  test('nested additionalProperties', (t) => {
@@ -203,7 +207,7 @@ test('nested additionalProperties', (t) => {
203
207
  })
204
208
 
205
209
  const obj = [{ ap: { value: 'string' } }]
206
- t.equal('[{"ap":{"value":"string"}}]', stringify(obj))
210
+ t.equal(stringify(obj), '[{"ap":{"value":"string"}}]')
207
211
  })
208
212
 
209
213
  test('very nested additionalProperties', (t) => {
@@ -240,7 +244,7 @@ test('very nested additionalProperties', (t) => {
240
244
  })
241
245
 
242
246
  const obj = [{ ap: { nested: { moarNested: { finally: { value: 'str' } } } } }]
243
- t.equal('[{"ap":{"nested":{"moarNested":{"finally":{"value":"str"}}}}}]', stringify(obj))
247
+ t.equal(stringify(obj), '[{"ap":{"nested":{"moarNested":{"finally":{"value":"str"}}}}}]')
244
248
  })
245
249
 
246
250
  test('nested additionalProperties set to true', (t) => {
@@ -257,7 +261,7 @@ test('nested additionalProperties set to true', (t) => {
257
261
  })
258
262
 
259
263
  const obj = { ap: { value: 'string', someNumber: 42 } }
260
- t.equal('{"ap":{"value":"string","someNumber":42}}', stringify(obj))
264
+ t.equal(stringify(obj), '{"ap":{"value":"string","someNumber":42}}')
261
265
  })
262
266
 
263
267
  test('field passed to fastSafeStringify as undefined should be removed', (t) => {
@@ -274,7 +278,7 @@ test('field passed to fastSafeStringify as undefined should be removed', (t) =>
274
278
  })
275
279
 
276
280
  const obj = { ap: { value: 'string', someNumber: undefined } }
277
- t.equal('{"ap":{"value":"string"}}', stringify(obj))
281
+ t.equal(stringify(obj), '{"ap":{"value":"string"}}')
278
282
  })
279
283
 
280
284
  test('property without type but with enum, will acts as additionalProperties', (t) => {
@@ -290,7 +294,7 @@ test('property without type but with enum, will acts as additionalProperties', (
290
294
  })
291
295
 
292
296
  const obj = { ap: { additional: 'field' } }
293
- t.equal('{"ap":{"additional":"field"}}', stringify(obj))
297
+ t.equal(stringify(obj), '{"ap":{"additional":"field"}}')
294
298
  })
295
299
 
296
300
  test('property without type but with enum, will acts as additionalProperties without overwriting', (t) => {
@@ -307,7 +311,7 @@ test('property without type but with enum, will acts as additionalProperties wit
307
311
  })
308
312
 
309
313
  const obj = { ap: { additional: 'field' } }
310
- t.equal('{"ap":{}}', stringify(obj))
314
+ t.equal(stringify(obj), '{"ap":{}}')
311
315
  })
312
316
 
313
317
  test('function and symbol references are not serialized as undefined', (t) => {
@@ -324,5 +328,5 @@ test('function and symbol references are not serialized as undefined', (t) => {
324
328
  })
325
329
 
326
330
  const obj = { str: 'x', test: 'test', meth: () => 'x', sym: Symbol('x') }
327
- t.equal('{"str":"x","test":"test"}', stringify(obj))
331
+ t.equal(stringify(obj), '{"str":"x","test":"test"}')
328
332
  })
@@ -1,8 +1,64 @@
1
1
  'use strict'
2
2
 
3
3
  const test = require('tap').test
4
+ const { DateTime } = require('luxon')
4
5
  const build = require('..')
5
6
 
7
+ test('allOf: combine type and format ', (t) => {
8
+ t.plan(1)
9
+
10
+ const schema = {
11
+ allOf: [
12
+ { type: 'string' },
13
+ { format: 'time' }
14
+ ]
15
+ }
16
+ const stringify = build(schema)
17
+ const date = new Date()
18
+ const value = stringify(date)
19
+ t.equal(value, `"${DateTime.fromJSDate(date).toFormat('HH:mm:ss')}"`)
20
+ })
21
+
22
+ test('allOf: combine additional properties ', (t) => {
23
+ t.plan(1)
24
+
25
+ const schema = {
26
+ allOf: [
27
+ { type: 'object' },
28
+ {
29
+ type: 'object',
30
+ additionalProperties: { type: 'boolean' }
31
+ }
32
+ ]
33
+ }
34
+ const stringify = build(schema)
35
+ const data = { property: true }
36
+ const value = stringify(data)
37
+ t.equal(value, JSON.stringify(data))
38
+ })
39
+
40
+ test('allOf: combine pattern properties', (t) => {
41
+ t.plan(1)
42
+
43
+ const schema = {
44
+ allOf: [
45
+ { type: 'object' },
46
+ {
47
+ type: 'object',
48
+ patternProperties: {
49
+ foo: {
50
+ type: 'number'
51
+ }
52
+ }
53
+ }
54
+ ]
55
+ }
56
+ const stringify = build(schema)
57
+ const data = { foo: 42 }
58
+ const value = stringify(data)
59
+ t.equal(value, JSON.stringify(data))
60
+ })
61
+
6
62
  test('object with allOf and multiple schema on the allOf', (t) => {
7
63
  t.plan(4)
8
64
 
@@ -1,5 +1,6 @@
1
1
  'use strict'
2
2
 
3
+ const { DateTime } = require('luxon')
3
4
  const { test } = require('tap')
4
5
  const build = require('..')
5
6
 
@@ -113,11 +114,7 @@ test('object with field of type string and coercion disable ', (t) => {
113
114
  }
114
115
  }
115
116
  const stringify = build(schema)
116
-
117
- const value = stringify({
118
- str: 1
119
- })
120
- t.equal(value, '{"str":null}')
117
+ t.throws(() => stringify({ str: 1 }))
121
118
  })
122
119
 
123
120
  test('object with field of type string and coercion enable ', (t) => {
@@ -224,7 +221,7 @@ test('symbol value in schema', (t) => {
224
221
  t.equal(stringify({ value: 'foo' }), '{"value":"foo"}')
225
222
  t.equal(stringify({ value: 'bar' }), '{"value":"bar"}')
226
223
  t.equal(stringify({ value: 'baz' }), '{"value":"baz"}')
227
- t.equal(stringify({ value: 'qux' }), '{"value":null}')
224
+ t.throws(() => stringify({ value: 'qux' }))
228
225
  })
229
226
 
230
227
  test('anyOf and $ref together', (t) => {
@@ -472,6 +469,90 @@ test('anyOf object with field date-time of type string with format or null', (t)
472
469
  }), `{"prop":"${toStringify.toISOString()}"}`)
473
470
  })
474
471
 
472
+ test('anyOf object with nested field date-time of type string with format or null', (t) => {
473
+ t.plan(1)
474
+ const withOneOfSchema = {
475
+ type: 'object',
476
+ properties: {
477
+ prop: {
478
+ anyOf: [{
479
+ type: 'object',
480
+ properties: {
481
+ nestedProp: {
482
+ type: 'string',
483
+ format: 'date-time'
484
+ }
485
+ }
486
+ }]
487
+ }
488
+ }
489
+ }
490
+
491
+ const withOneOfStringify = build(withOneOfSchema)
492
+
493
+ const data = {
494
+ prop: { nestedProp: new Date() }
495
+ }
496
+
497
+ t.equal(withOneOfStringify(data), JSON.stringify(data))
498
+ })
499
+
500
+ test('anyOf object with nested field date of type string with format or null', (t) => {
501
+ t.plan(1)
502
+ const withOneOfSchema = {
503
+ type: 'object',
504
+ properties: {
505
+ prop: {
506
+ anyOf: [{
507
+ type: 'object',
508
+ properties: {
509
+ nestedProp: {
510
+ type: 'string',
511
+ format: 'date'
512
+ }
513
+ }
514
+ }]
515
+ }
516
+ }
517
+ }
518
+
519
+ const withOneOfStringify = build(withOneOfSchema)
520
+
521
+ const data = {
522
+ prop: { nestedProp: new Date() }
523
+ }
524
+
525
+ t.equal(withOneOfStringify(data), `{"prop":{"nestedProp":"${DateTime.fromJSDate(data.prop.nestedProp).toISODate()}"}}`)
526
+ })
527
+
528
+ test('anyOf object with nested field time of type string with format or null', (t) => {
529
+ t.plan(1)
530
+ const withOneOfSchema = {
531
+ type: 'object',
532
+ properties: {
533
+ prop: {
534
+ anyOf: [{
535
+ type: 'object',
536
+ properties: {
537
+ nestedProp: {
538
+ type: 'string',
539
+ format: 'time'
540
+ }
541
+ }
542
+ }]
543
+ }
544
+ }
545
+ }
546
+
547
+ const withOneOfStringify = build(withOneOfSchema)
548
+
549
+ const data = {
550
+ prop: { nestedProp: new Date() }
551
+ }
552
+
553
+ t.equal(withOneOfStringify(data), `{"prop":{"nestedProp":"${DateTime.fromJSDate(data.prop.nestedProp).toFormat('HH:mm:ss')}"}}`)
554
+ })
555
+
475
556
  test('anyOf object with field date of type string with format or null', (t) => {
476
557
  t.plan(1)
477
558
  const toStringify = '2011-01-01'
@@ -513,7 +594,5 @@ test('anyOf object with invalid field date of type string with format or null',
513
594
  }
514
595
 
515
596
  const withOneOfStringify = build(withOneOfSchema)
516
- t.equal(withOneOfStringify({
517
- prop: toStringify
518
- }), '{"prop":null}')
597
+ t.throws(() => withOneOfStringify({ prop: toStringify }))
519
598
  })
@@ -1,16 +1,15 @@
1
1
  'use strict'
2
2
 
3
- const moment = require('moment')
4
3
  const test = require('tap').test
5
4
  const validator = require('is-my-json-valid')
6
5
  const build = require('..')
7
6
 
8
- function buildTest (schema, toStringify) {
7
+ function buildTest (schema, toStringify, options) {
9
8
  test(`render a ${schema.title} as JSON`, (t) => {
10
9
  t.plan(3)
11
10
 
12
11
  const validate = validator(schema)
13
- const stringify = build(schema)
12
+ const stringify = build(schema, options)
14
13
  const output = stringify(toStringify)
15
14
 
16
15
  t.same(JSON.parse(output), toStringify)
@@ -177,27 +176,6 @@ test('invalid items throw', (t) => {
177
176
  t.throws(() => stringify({ args: ['invalid'] }))
178
177
  })
179
178
 
180
- test('moment array', (t) => {
181
- t.plan(1)
182
- const schema = {
183
- type: 'object',
184
- properties: {
185
- times: {
186
- type: 'array',
187
- items: {
188
- type: 'string',
189
- format: 'date-time'
190
- }
191
- }
192
- }
193
- }
194
- const stringify = build(schema)
195
- const value = stringify({
196
- times: [moment('2018-04-21T07:52:31.017Z')]
197
- })
198
- t.equal(value, '{"times":["2018-04-21T07:52:31.017Z"]}')
199
- })
200
-
201
179
  buildTest({
202
180
  title: 'item types in array default to any',
203
181
  type: 'object',
@@ -319,3 +297,127 @@ test('object array with anyOf and symbol', (t) => {
319
297
  ])
320
298
  t.equal(value, '[{"name":"name-0","option":"Foo"},{"name":"name-1","option":"Bar"}]')
321
299
  })
300
+
301
+ const largeArray = new Array(2e4).fill({ a: 'test', b: 1 })
302
+ buildTest({
303
+ title: 'large array with default mechanism',
304
+ type: 'object',
305
+ properties: {
306
+ ids: {
307
+ type: 'array',
308
+ items: {
309
+ type: 'object',
310
+ properties: {
311
+ a: { type: 'string' },
312
+ b: { type: 'number' }
313
+ }
314
+ }
315
+ }
316
+ }
317
+ }, {
318
+ ids: largeArray
319
+ }, {
320
+ largeArraySize: 2e4,
321
+ largeArrayMechanism: 'default'
322
+ })
323
+
324
+ buildTest({
325
+ title: 'large array of objects with json-stringify mechanism',
326
+ type: 'object',
327
+ properties: {
328
+ ids: {
329
+ type: 'array',
330
+ items: {
331
+ type: 'object',
332
+ properties: {
333
+ a: { type: 'string' },
334
+ b: { type: 'number' }
335
+ }
336
+ }
337
+ }
338
+ }
339
+ }, {
340
+ ids: largeArray
341
+ }, {
342
+ largeArrayMechanism: 'json-stringify'
343
+ })
344
+
345
+ buildTest({
346
+ title: 'large array of strings with default mechanism',
347
+ type: 'object',
348
+ properties: {
349
+ ids: {
350
+ type: 'array',
351
+ items: { type: 'string' }
352
+ }
353
+ }
354
+ }, {
355
+ ids: new Array(2e4).fill('string')
356
+ }, {
357
+ largeArraySize: 2e4,
358
+ largeArrayMechanism: 'default'
359
+ })
360
+
361
+ buildTest({
362
+ title: 'large array of numbers with default mechanism',
363
+ type: 'object',
364
+ properties: {
365
+ ids: {
366
+ type: 'array',
367
+ items: { type: 'number' }
368
+ }
369
+ }
370
+ }, {
371
+ ids: new Array(2e4).fill(42)
372
+ }, {
373
+ largeArraySize: 2e4,
374
+ largeArrayMechanism: 'default'
375
+ })
376
+
377
+ buildTest({
378
+ title: 'large array of integers with default mechanism',
379
+ type: 'object',
380
+ properties: {
381
+ ids: {
382
+ type: 'array',
383
+ items: { type: 'integer' }
384
+ }
385
+ }
386
+ }, {
387
+ ids: new Array(2e4).fill(42)
388
+ }, {
389
+ largeArraySize: 2e4,
390
+ largeArrayMechanism: 'default'
391
+ })
392
+
393
+ buildTest({
394
+ title: 'large array of booleans with default mechanism',
395
+ type: 'object',
396
+ properties: {
397
+ ids: {
398
+ type: 'array',
399
+ items: { type: 'boolean' }
400
+ }
401
+ }
402
+ }, {
403
+ ids: new Array(2e4).fill(true)
404
+ }, {
405
+ largeArraySize: 2e4,
406
+ largeArrayMechanism: 'default'
407
+ })
408
+
409
+ buildTest({
410
+ title: 'large array of null values with default mechanism',
411
+ type: 'object',
412
+ properties: {
413
+ ids: {
414
+ type: 'array',
415
+ items: { type: 'null' }
416
+ }
417
+ }
418
+ }, {
419
+ ids: new Array(2e4).fill(null)
420
+ }, {
421
+ largeArraySize: 2e4,
422
+ largeArrayMechanism: 'default'
423
+ })
@@ -261,7 +261,7 @@ buildTest({
261
261
  readonly: true
262
262
  })
263
263
 
264
- test('skip or coerce numbers and integers that are not numbers', (t) => {
264
+ test('throw an error or coerce numbers and integers that are not numbers', (t) => {
265
265
  const stringify = build({
266
266
  title: 'basic',
267
267
  type: 'object',
@@ -275,14 +275,14 @@ test('skip or coerce numbers and integers that are not numbers', (t) => {
275
275
  }
276
276
  })
277
277
 
278
- let result = stringify({
279
- age: 'hello ',
280
- distance: 'long'
281
- })
282
-
283
- t.same(JSON.parse(result), {})
278
+ try {
279
+ stringify({ age: 'hello ', distance: 'long' })
280
+ t.fail('should throw an error')
281
+ } catch (err) {
282
+ t.ok(err)
283
+ }
284
284
 
285
- result = stringify({
285
+ const result = stringify({
286
286
  age: '42',
287
287
  distance: true
288
288
  })