fast-json-stringify 6.2.0 → 6.3.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/README.md CHANGED
@@ -79,7 +79,6 @@ compile-json-stringify date format x 803,214 ops/sec ±0.23% (97 runs sampled)
79
79
 
80
80
 
81
81
  <a name="example"></a>
82
- Try it out on RunKit: <a href="https://runkit.com/npm/fast-json-stringify">https://runkit.com/npm/fast-json-stringify</a>
83
82
  ## Example
84
83
 
85
84
  ```js
@@ -347,9 +346,9 @@ const stringify = fastJson({
347
346
  properties: {
348
347
  'undecidedType': {
349
348
  'anyOf': [{
350
- type: 'string'
349
+ type: 'string'
351
350
  }, {
352
- type: 'boolean'
351
+ type: 'boolean'
353
352
  }]
354
353
  }
355
354
  }
@@ -645,8 +644,8 @@ const stringify = fastJson({
645
644
  type: 'object',
646
645
  properties: {
647
646
  'code': {
648
- type: 'string',
649
- format 'unsafe'
647
+ type: 'string',
648
+ format: 'unsafe'
650
649
  }
651
650
  }
652
651
  })
@@ -84,13 +84,13 @@ const arraySchemaAJVJTD = {
84
84
  const dateFormatSchema = {
85
85
  description: 'Date of birth',
86
86
  type: 'string',
87
- format: 'date'
87
+ format: 'datetime'
88
88
  }
89
89
 
90
90
  const dateFormatSchemaCJS = {
91
91
  description: 'Date of birth',
92
92
  type: 'string',
93
- format: 'date'
93
+ format: 'datetime'
94
94
  }
95
95
 
96
96
  const obj = {
@@ -310,9 +310,9 @@ suite.add('compile-json-stringify date format', function () {
310
310
 
311
311
  suite.run().then(() => {
312
312
  for (const task of suite.tasks) {
313
- const hz = task.result.hz // ops/sec
314
- const rme = task.result.rme // relative margin of error (%)
315
- const samples = task.result.samples.length
313
+ const hz = task.result.throughput.mean // ops/sec
314
+ const rme = task.result.latency.rme // relative margin of error (%)
315
+ const samples = task.result.latency.df + 1 // degrees of freedom + 1 = sample count
316
316
 
317
317
  const formattedHz = hz.toLocaleString('en-US', { maximumFractionDigits: 0 })
318
318
  const formattedRme = rme.toFixed(2)
@@ -21,9 +21,9 @@ bench.add(benchmark.name, () => {
21
21
  stringify(benchmark.input)
22
22
  }).run().then(() => {
23
23
  const task = bench.tasks[0]
24
- const hz = task.result.hz // ops/sec
25
- const rme = task.result.rme // relative margin of error (%)
26
- const samples = task.result.samples.length
24
+ const hz = task.result.throughput.mean // ops/sec
25
+ const rme = task.result.latency.rme // relative margin of error (%)
26
+ const samples = task.result.latency.df + 1 // degrees of freedom + 1 = sample count
27
27
 
28
28
  const formattedHz = hz.toLocaleString('en-US', { maximumFractionDigits: 0 })
29
29
  const formattedRme = rme.toFixed(2)
package/index.js CHANGED
@@ -304,14 +304,14 @@ function buildExtraObjectPropertiesSerializer (context, location, addComma, objV
304
304
  const propertiesKeys = Object.keys(schema.properties || {})
305
305
 
306
306
  let code = `
307
- const propertiesKeys = ${JSON.stringify(propertiesKeys)}
308
- for (const [key, value] of Object.entries(${objVar})) {
307
+ for (const key of Object.keys(${objVar})) {
309
308
  if (
310
- propertiesKeys.includes(key) ||
311
- value === undefined ||
312
- typeof value === 'function' ||
313
- typeof value === 'symbol'
309
+ ${propertiesKeys.length > 0 ? propertiesKeys.map(k => `key === ${JSON.stringify(k)}`).join(' || ') + ' ||' : ''}
310
+ ${objVar}[key] === undefined ||
311
+ typeof ${objVar}[key] === 'function' ||
312
+ typeof ${objVar}[key] === 'symbol'
314
313
  ) continue
314
+ const value = ${objVar}[key]
315
315
  `
316
316
 
317
317
  const patternPropertiesLocation = location.getPropertyLocation('patternProperties')
@@ -385,45 +385,99 @@ function buildInnerObject (context, location, objVar) {
385
385
 
386
386
  const localUid = context.uid++
387
387
  let addComma = ''
388
- const needsRuntimeComma = propertiesKeys.length > 1 || schema.patternProperties || (schema.additionalProperties !== undefined && schema.additionalProperties !== false)
389
388
 
390
- if (needsRuntimeComma) {
391
- code += `let addComma_${localUid} = false\n`
392
- addComma = `!addComma_${localUid} && (addComma_${localUid} = true) || (json += JSON_STR_COMMA)`
393
- }
389
+ if (requiredProperties.length > 0) {
390
+ // If we have required properties, we know that at least one property will be serialized.
391
+ // We can avoid the runtime check for the comma.
394
392
 
395
- for (const key of propertiesKeys) {
396
- let propertyLocation = propertiesLocation.getPropertyLocation(key)
397
- if (propertyLocation.schema.$ref) {
398
- propertyLocation = resolveRef(context, propertyLocation)
399
- }
393
+ // The first property is required, so we don't need a comma.
394
+ // For the subsequent properties, we can blindly add a comma.
400
395
 
401
- const sanitizedKey = JSON.stringify(key)
402
- const value = `value_${key.replace(/[^a-zA-Z0-9]/g, '_')}_${context.uid++}`
403
- const defaultValue = propertyLocation.schema.default
404
- const isRequired = requiredProperties.includes(key)
396
+ for (let i = 0; i < propertiesKeys.length; i++) {
397
+ const key = propertiesKeys[i]
398
+ const propertyLocation = propertiesLocation.getPropertyLocation(key)
405
399
 
406
- code += `
400
+ let resolvedLocation = propertyLocation
401
+ if (propertyLocation.schema.$ref) {
402
+ resolvedLocation = resolveRef(context, propertyLocation)
403
+ }
404
+
405
+ const sanitizedKey = JSON.stringify(key)
406
+ const value = `value_${key.replace(/[^a-zA-Z0-9]/g, '_')}_${context.uid++}`
407
+ const defaultValue = resolvedLocation.schema.default
408
+ const isRequired = requiredProperties.includes(key)
409
+
410
+ // i === 0 means it's the first property, and it IS required (due to sort). No comma.
411
+ // i > 0 means it follows a required property (or a sequence starting with one). Unconditional comma.
412
+ const currentAddComma = i === 0 ? '' : 'json += JSON_STR_COMMA'
413
+
414
+ code += `
407
415
  const ${value} = ${objVar}[${sanitizedKey}]
408
416
  if (${value} !== undefined) {
409
- ${addComma}
417
+ ${currentAddComma}
410
418
  json += ${JSON.stringify(sanitizedKey + ':')}
411
- ${buildValue(context, propertyLocation, `${value}`)}
419
+ ${buildValue(context, resolvedLocation, `${value}`)}
412
420
  }`
413
421
 
414
- if (defaultValue !== undefined) {
415
- code += ` else {
416
- ${addComma}
422
+ if (defaultValue !== undefined) {
423
+ code += ` else {
424
+ ${currentAddComma}
417
425
  json += ${JSON.stringify(sanitizedKey + ':' + JSON.stringify(defaultValue))}
418
426
  }
419
427
  `
420
- } else if (isRequired) {
421
- code += ` else {
428
+ } else if (isRequired) {
429
+ code += ` else {
422
430
  throw new Error('${sanitizedKey.replace(/'/g, '\\\'')} is required!')
423
431
  }
424
432
  `
425
- } else {
426
- code += '\n'
433
+ } else {
434
+ code += '\n'
435
+ }
436
+ }
437
+
438
+ addComma = 'json += JSON_STR_COMMA'
439
+ } else {
440
+ const needsRuntimeComma = propertiesKeys.length > 1 || schema.patternProperties || (schema.additionalProperties !== undefined && schema.additionalProperties !== false)
441
+
442
+ if (needsRuntimeComma) {
443
+ code += `let addComma_${localUid} = false\n`
444
+ addComma = `!addComma_${localUid} && (addComma_${localUid} = true) || (json += JSON_STR_COMMA)`
445
+ }
446
+
447
+ for (const key of propertiesKeys) {
448
+ let propertyLocation = propertiesLocation.getPropertyLocation(key)
449
+ if (propertyLocation.schema.$ref) {
450
+ propertyLocation = resolveRef(context, propertyLocation)
451
+ }
452
+
453
+ const sanitizedKey = JSON.stringify(key)
454
+ const value = `value_${key.replace(/[^a-zA-Z0-9]/g, '_')}_${context.uid++}`
455
+ const defaultValue = propertyLocation.schema.default
456
+ const isRequired = requiredProperties.includes(key) // Should be false here but good to keep
457
+
458
+ code += `
459
+ const ${value} = ${objVar}[${sanitizedKey}]
460
+ if (${value} !== undefined) {
461
+ ${addComma}
462
+ json += ${JSON.stringify(sanitizedKey + ':')}
463
+ ${buildValue(context, propertyLocation, `${value}`)}
464
+ }`
465
+
466
+ if (defaultValue !== undefined) {
467
+ code += ` else {
468
+ ${addComma}
469
+ json += ${JSON.stringify(sanitizedKey + ':' + JSON.stringify(defaultValue))}
470
+ }
471
+ `
472
+ } else if (isRequired) {
473
+ // Should not happen if requiredProperties.length === 0 but safety
474
+ code += ` else {
475
+ throw new Error('${sanitizedKey.replace(/'/g, '\\\'')} is required!')
476
+ }
477
+ `
478
+ } else {
479
+ code += '\n'
480
+ }
427
481
  }
428
482
  }
429
483
 
@@ -611,10 +665,15 @@ function buildArray (context, location, input) {
611
665
 
612
666
  if (Array.isArray(itemsSchema)) {
613
667
  for (let i = 0, itemsSchemaLength = itemsSchema.length; i < itemsSchemaLength; i++) {
614
- const item = itemsSchema[i]
668
+ let item = itemsSchema[i]
669
+ let itemLocation = itemsLocation.getPropertyLocation(i)
670
+ if (itemLocation.schema.$ref) {
671
+ itemLocation = resolveRef(context, itemLocation)
672
+ item = itemLocation.schema
673
+ }
615
674
  const value = `value_${i}`
616
675
  functionCode += `const ${value} = obj[${i}]`
617
- const tmpRes = buildValue(context, itemsLocation.getPropertyLocation(i), value)
676
+ const tmpRes = buildValue(context, itemLocation, value)
618
677
  functionCode += `
619
678
  if (${i} < arrayLength) {
620
679
  if (${buildArrayTypeCondition(item.type, value)}) {
@@ -641,12 +700,14 @@ function buildArray (context, location, input) {
641
700
  } else {
642
701
  const code = buildValue(context, itemsLocation, 'value')
643
702
  functionCode += `
644
- for (let i = 0; i < arrayLength; i++) {
645
- if (i) {
703
+ if (arrayLength > 0) {
704
+ const value = obj[0]
705
+ ${code}
706
+ for (let i = 1; i < arrayLength; i++) {
646
707
  json += JSON_STR_COMMA
708
+ const value = obj[i]
709
+ ${code}
647
710
  }
648
- const value = obj[i]
649
- ${code}
650
711
  }`
651
712
  }
652
713
 
@@ -691,10 +752,15 @@ function buildArray (context, location, input) {
691
752
  const localUid = context.uid++
692
753
  inlinedCode += `let addComma_${localUid} = false\n`
693
754
  for (let i = 0, itemsSchemaLength = itemsSchema.length; i < itemsSchemaLength; i++) {
694
- const item = itemsSchema[i]
755
+ let item = itemsSchema[i]
756
+ let itemLocation = itemsLocation.getPropertyLocation(i)
757
+ if (itemLocation.schema.$ref) {
758
+ itemLocation = resolveRef(context, itemLocation)
759
+ item = itemLocation.schema
760
+ }
695
761
  const value = `value_${i}_${context.uid++}`
696
762
  inlinedCode += `const ${value} = ${objVar}[${i}]`
697
- const tmpRes = buildValue(context, itemsLocation.getPropertyLocation(i), value)
763
+ const tmpRes = buildValue(context, itemLocation, value)
698
764
  inlinedCode += `
699
765
  if (${i} < arrayLength_${objVar}) {
700
766
  if (${buildArrayTypeCondition(item.type, value)}) {
@@ -717,12 +783,14 @@ function buildArray (context, location, input) {
717
783
  } else {
718
784
  const code = buildValue(context, itemsLocation, 'value')
719
785
  inlinedCode += `
720
- for (let i = 0; i < arrayLength_${objVar}; i++) {
721
- if (i) {
786
+ if (arrayLength_${objVar} > 0) {
787
+ const value = ${objVar}[0]
788
+ ${code}
789
+ for (let i = 1; i < arrayLength_${objVar}; i++) {
722
790
  json += JSON_STR_COMMA
791
+ const value = ${objVar}[i]
792
+ ${code}
723
793
  }
724
- const value = ${objVar}[i]
725
- ${code}
726
794
  }`
727
795
  }
728
796
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fast-json-stringify",
3
- "version": "6.2.0",
3
+ "version": "6.3.0",
4
4
  "description": "Stringify your JSON at max speed",
5
5
  "main": "index.js",
6
6
  "type": "commonjs",
@@ -62,19 +62,20 @@
62
62
  }
63
63
  ],
64
64
  "devDependencies": {
65
- "@sinclair/typebox": "^0.34.3",
66
- "c8": "^10.1.2",
65
+ "@sinclair/typebox": "^0.34.48",
66
+ "c8": "^10.1.3",
67
67
  "cli-select": "^1.1.2",
68
68
  "compile-json-stringify": "^0.1.2",
69
- "eslint": "^9.17.0",
69
+ "eslint": "^9.39.2",
70
70
  "fast-json-stringify": ".",
71
71
  "is-my-json-valid": "^2.20.6",
72
- "json-accelerator": "^0.0.2",
73
- "neostandard": "^0.12.0",
74
- "simple-git": "^3.23.0",
75
- "tinybench": "^5.0.1",
76
- "tsd": "^0.32.0",
77
- "webpack": "^5.90.3"
72
+ "json-accelerator": "^0.1.7",
73
+ "neostandard": "^0.12.2",
74
+ "simple-git": "^3.30.0",
75
+ "tinybench": "^6.0.0",
76
+ "tsd": "^0.33.0",
77
+ "typescript": "^5.9.3",
78
+ "webpack": "^5.104.1"
78
79
  },
79
80
  "dependencies": {
80
81
  "@fastify/merge-json-schemas": "^0.2.0",
@@ -61,6 +61,34 @@ buildTest({
61
61
  dates: [new Date(1), new Date(2)]
62
62
  })
63
63
 
64
+ buildTest({
65
+ title: 'dates tuple $ref',
66
+ definitions: {
67
+ dateTime: {
68
+ type: 'string',
69
+ format: 'date-time'
70
+ }
71
+ },
72
+ type: 'object',
73
+ properties: {
74
+ dates: {
75
+ type: 'array',
76
+ minItems: 2,
77
+ maxItems: 2,
78
+ items: [
79
+ {
80
+ $ref: '#/definitions/dateTime'
81
+ },
82
+ {
83
+ $ref: '#/definitions/dateTime'
84
+ }
85
+ ]
86
+ }
87
+ }
88
+ }, {
89
+ dates: [new Date(1), new Date(2)]
90
+ })
91
+
64
92
  buildTest({
65
93
  title: 'string array',
66
94
  type: 'object',