fast-json-stringify 5.0.6 → 5.1.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/ajv.js +13 -20
- package/index.js +58 -27
- package/package.json +2 -2
- package/serializer.js +12 -12
- package/test/basic.test.js +13 -0
- package/test/date.test.js +100 -4
package/ajv.js
CHANGED
|
@@ -7,29 +7,22 @@ const ajvFormats = require('ajv-formats')
|
|
|
7
7
|
module.exports = buildAjv
|
|
8
8
|
|
|
9
9
|
function buildAjv (options) {
|
|
10
|
-
const ajvInstance = new Ajv({
|
|
11
|
-
|
|
10
|
+
const ajvInstance = new Ajv({
|
|
11
|
+
...options,
|
|
12
|
+
strictSchema: false,
|
|
13
|
+
validateSchema: false,
|
|
14
|
+
allowUnionTypes: true,
|
|
15
|
+
uriResolver: fastUri
|
|
16
|
+
})
|
|
12
17
|
|
|
13
|
-
|
|
14
|
-
const validateDateFormat = ajvFormats.get('date').validate
|
|
15
|
-
const validateTimeFormat = ajvFormats.get('time').validate
|
|
18
|
+
ajvFormats(ajvInstance)
|
|
16
19
|
|
|
17
20
|
ajvInstance.addKeyword({
|
|
18
|
-
keyword: '
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
if (schema === 'date-time') {
|
|
24
|
-
return validateDateTimeFormat(date)
|
|
25
|
-
}
|
|
26
|
-
if (schema === 'date') {
|
|
27
|
-
return validateDateFormat(date)
|
|
28
|
-
}
|
|
29
|
-
if (schema === 'time') {
|
|
30
|
-
return validateTimeFormat(date)
|
|
31
|
-
}
|
|
32
|
-
return false
|
|
21
|
+
keyword: 'fjs_type',
|
|
22
|
+
type: 'object',
|
|
23
|
+
errors: false,
|
|
24
|
+
validate: (type, date) => {
|
|
25
|
+
return date instanceof Date
|
|
33
26
|
}
|
|
34
27
|
})
|
|
35
28
|
|
package/index.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
/* eslint no-prototype-builtins: 0 */
|
|
4
4
|
|
|
5
|
-
const merge = require('deepmerge')
|
|
5
|
+
const merge = require('@fastify/deepmerge')()
|
|
6
6
|
const clone = require('rfdc')({ proto: true })
|
|
7
7
|
const fjsCloned = Symbol('fast-json-stringify.cloned')
|
|
8
8
|
const { randomUUID } = require('crypto')
|
|
@@ -243,15 +243,6 @@ function inferTypeByKeyword (schema) {
|
|
|
243
243
|
return schema.type
|
|
244
244
|
}
|
|
245
245
|
|
|
246
|
-
function getStringSerializer (format, nullable) {
|
|
247
|
-
switch (format) {
|
|
248
|
-
case 'date-time': return nullable ? 'serializer.asDatetimeNullable.bind(serializer)' : 'serializer.asDatetime.bind(serializer)'
|
|
249
|
-
case 'date': return nullable ? 'serializer.asDateNullable.bind(serializer)' : 'serializer.asDate.bind(serializer)'
|
|
250
|
-
case 'time': return nullable ? 'serializer.asTimeNullable.bind(serializer)' : 'serializer.asTime.bind(serializer)'
|
|
251
|
-
default: return nullable ? 'serializer.asStringNullable.bind(serializer)' : 'serializer.asString.bind(serializer)'
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
|
|
255
246
|
function addPatternProperties (location) {
|
|
256
247
|
const schema = location.schema
|
|
257
248
|
const pp = schema.patternProperties
|
|
@@ -484,6 +475,16 @@ function mergeAllOfSchema (location, schema, mergedSchema) {
|
|
|
484
475
|
mergedSchema.anyOf.push(...allOfSchema.anyOf)
|
|
485
476
|
}
|
|
486
477
|
|
|
478
|
+
if (allOfSchema.fjs_type !== undefined) {
|
|
479
|
+
if (
|
|
480
|
+
mergedSchema.fjs_type !== undefined &&
|
|
481
|
+
mergedSchema.fjs_type !== allOfSchema.fjs_type
|
|
482
|
+
) {
|
|
483
|
+
throw new Error('allOf schemas have different fjs_type values')
|
|
484
|
+
}
|
|
485
|
+
mergedSchema.fjs_type = allOfSchema.fjs_type
|
|
486
|
+
}
|
|
487
|
+
|
|
487
488
|
if (allOfSchema.allOf !== undefined) {
|
|
488
489
|
mergeAllOfSchema(location, allOfSchema, mergedSchema)
|
|
489
490
|
}
|
|
@@ -771,6 +772,10 @@ function generateFuncName () {
|
|
|
771
772
|
function buildValue (location, input) {
|
|
772
773
|
let schema = location.schema
|
|
773
774
|
|
|
775
|
+
if (typeof schema === 'boolean') {
|
|
776
|
+
return `json += JSON.stringify(${input})`
|
|
777
|
+
}
|
|
778
|
+
|
|
774
779
|
if (schema.$ref) {
|
|
775
780
|
location = resolveRef(location, schema.$ref)
|
|
776
781
|
schema = location.schema
|
|
@@ -790,20 +795,22 @@ function buildValue (location, input) {
|
|
|
790
795
|
location.schema = mergedSchema
|
|
791
796
|
}
|
|
792
797
|
|
|
793
|
-
|
|
798
|
+
let type = schema.type
|
|
794
799
|
const nullable = schema.nullable === true
|
|
795
800
|
|
|
796
801
|
let code = ''
|
|
797
802
|
let funcName
|
|
798
803
|
|
|
804
|
+
if (schema.fjs_type === 'string' && schema.format === undefined && Array.isArray(schema.type) && schema.type.length === 2) {
|
|
805
|
+
type = 'string'
|
|
806
|
+
}
|
|
807
|
+
|
|
799
808
|
switch (type) {
|
|
800
809
|
case 'null':
|
|
801
|
-
code +=
|
|
802
|
-
json += serializer.asNull()
|
|
803
|
-
`
|
|
810
|
+
code += 'json += serializer.asNull()'
|
|
804
811
|
break
|
|
805
812
|
case 'string': {
|
|
806
|
-
funcName =
|
|
813
|
+
funcName = nullable ? 'serializer.asStringNullable.bind(serializer)' : 'serializer.asString.bind(serializer)'
|
|
807
814
|
code += `json += ${funcName}(${input})`
|
|
808
815
|
break
|
|
809
816
|
}
|
|
@@ -820,7 +827,15 @@ function buildValue (location, input) {
|
|
|
820
827
|
code += `json += ${funcName}(${input})`
|
|
821
828
|
break
|
|
822
829
|
case 'object':
|
|
823
|
-
|
|
830
|
+
if (schema.format === 'date-time') {
|
|
831
|
+
funcName = nullable ? 'serializer.asDateTimeNullable.bind(serializer)' : 'serializer.asDateTime.bind(serializer)'
|
|
832
|
+
} else if (schema.format === 'date') {
|
|
833
|
+
funcName = nullable ? 'serializer.asDateNullable.bind(serializer)' : 'serializer.asDate.bind(serializer)'
|
|
834
|
+
} else if (schema.format === 'time') {
|
|
835
|
+
funcName = nullable ? 'serializer.asTimeNullable.bind(serializer)' : 'serializer.asTime.bind(serializer)'
|
|
836
|
+
} else {
|
|
837
|
+
funcName = buildObject(location)
|
|
838
|
+
}
|
|
824
839
|
code += `json += ${funcName}(${input})`
|
|
825
840
|
break
|
|
826
841
|
case 'array':
|
|
@@ -828,11 +843,7 @@ function buildValue (location, input) {
|
|
|
828
843
|
code += `json += ${funcName}(${input})`
|
|
829
844
|
break
|
|
830
845
|
case undefined:
|
|
831
|
-
if (schema.
|
|
832
|
-
funcName = getStringSerializer(schema.fjs_date_type, nullable)
|
|
833
|
-
code += `json += ${funcName}(${input})`
|
|
834
|
-
break
|
|
835
|
-
} else if (schema.anyOf || schema.oneOf) {
|
|
846
|
+
if (schema.anyOf || schema.oneOf) {
|
|
836
847
|
// beware: dereferenceOfRefs has side effects and changes schema.anyOf
|
|
837
848
|
const type = schema.anyOf ? 'anyOf' : 'oneOf'
|
|
838
849
|
const anyOfLocation = mergeLocation(location, type)
|
|
@@ -890,7 +901,7 @@ function buildValue (location, input) {
|
|
|
890
901
|
switch (type) {
|
|
891
902
|
case 'string': {
|
|
892
903
|
code += `
|
|
893
|
-
${statement}(${input} === null || typeof ${input} === "${type}" || ${input} instanceof
|
|
904
|
+
${statement}(${input} === null || typeof ${input} === "${type}" || ${input} instanceof RegExp || (typeof ${input} === "object" && Object.hasOwnProperty.call(${input}, "toString")))
|
|
894
905
|
${nestedResult}
|
|
895
906
|
`
|
|
896
907
|
break
|
|
@@ -909,6 +920,20 @@ function buildValue (location, input) {
|
|
|
909
920
|
`
|
|
910
921
|
break
|
|
911
922
|
}
|
|
923
|
+
case 'object': {
|
|
924
|
+
if (schema.fjs_type) {
|
|
925
|
+
code += `
|
|
926
|
+
${statement}(${input} instanceof Date || ${input} === null)
|
|
927
|
+
${nestedResult}
|
|
928
|
+
`
|
|
929
|
+
} else {
|
|
930
|
+
code += `
|
|
931
|
+
${statement}(typeof ${input} === "object" || ${input} === null)
|
|
932
|
+
${nestedResult}
|
|
933
|
+
`
|
|
934
|
+
}
|
|
935
|
+
break
|
|
936
|
+
}
|
|
912
937
|
default: {
|
|
913
938
|
code += `
|
|
914
939
|
${statement}(typeof ${input} === "${type}" || ${input} === null)
|
|
@@ -936,15 +961,21 @@ function buildValue (location, input) {
|
|
|
936
961
|
}
|
|
937
962
|
|
|
938
963
|
// Ajv does not support js date format. In order to properly validate objects containing a date,
|
|
939
|
-
// it needs to replace all occurrences of the string date format with a custom keyword
|
|
964
|
+
// it needs to replace all occurrences of the string date format with a custom keyword fjs_type.
|
|
940
965
|
// (see https://github.com/fastify/fast-json-stringify/pull/441)
|
|
941
966
|
function extendDateTimeType (schema) {
|
|
942
967
|
if (schema === null) return
|
|
943
968
|
|
|
944
|
-
if (schema.type === 'string'
|
|
945
|
-
schema.
|
|
946
|
-
|
|
947
|
-
|
|
969
|
+
if (schema.type === 'string') {
|
|
970
|
+
schema.fjs_type = 'string'
|
|
971
|
+
schema.type = ['string', 'object']
|
|
972
|
+
} else if (
|
|
973
|
+
Array.isArray(schema.type) &&
|
|
974
|
+
schema.type.includes('string') &&
|
|
975
|
+
!schema.type.includes('object')
|
|
976
|
+
) {
|
|
977
|
+
schema.fjs_type = 'string'
|
|
978
|
+
schema.type.push('object')
|
|
948
979
|
}
|
|
949
980
|
for (const property in schema) {
|
|
950
981
|
if (typeof schema[property] === 'object') {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fast-json-stringify",
|
|
3
|
-
"version": "5.0
|
|
3
|
+
"version": "5.1.0",
|
|
4
4
|
"description": "Stringify your JSON at max speed",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"types": "index.d.ts",
|
|
@@ -49,9 +49,9 @@
|
|
|
49
49
|
"webpack": "^5.40.0"
|
|
50
50
|
},
|
|
51
51
|
"dependencies": {
|
|
52
|
+
"@fastify/deepmerge": "^1.0.0",
|
|
52
53
|
"ajv": "^8.10.0",
|
|
53
54
|
"ajv-formats": "^2.1.1",
|
|
54
|
-
"deepmerge": "^4.2.2",
|
|
55
55
|
"fast-uri": "^2.1.0",
|
|
56
56
|
"rfdc": "^1.2.0"
|
|
57
57
|
},
|
package/serializer.js
CHANGED
|
@@ -69,24 +69,24 @@ module.exports = class Serializer {
|
|
|
69
69
|
return bool === null ? 'null' : this.asBoolean(bool)
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
-
|
|
73
|
-
|
|
72
|
+
asDateTime (date) {
|
|
73
|
+
if (date === null) return '""'
|
|
74
74
|
if (date instanceof Date) {
|
|
75
|
-
return
|
|
75
|
+
return '"' + date.toISOString() + '"'
|
|
76
76
|
}
|
|
77
|
-
|
|
77
|
+
throw new Error(`The value "${date}" cannot be converted to a date-time.`)
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
-
|
|
81
|
-
return date === null ? 'null' : this.
|
|
80
|
+
asDateTimeNullable (date) {
|
|
81
|
+
return date === null ? 'null' : this.asDateTime(date)
|
|
82
82
|
}
|
|
83
83
|
|
|
84
84
|
asDate (date) {
|
|
85
|
-
|
|
85
|
+
if (date === null) return '""'
|
|
86
86
|
if (date instanceof Date) {
|
|
87
|
-
return
|
|
87
|
+
return '"' + new Date(date.getTime() - (date.getTimezoneOffset() * 60000)).toISOString().slice(0, 10) + '"'
|
|
88
88
|
}
|
|
89
|
-
|
|
89
|
+
throw new Error(`The value "${date}" cannot be converted to a date.`)
|
|
90
90
|
}
|
|
91
91
|
|
|
92
92
|
asDateNullable (date) {
|
|
@@ -94,11 +94,11 @@ module.exports = class Serializer {
|
|
|
94
94
|
}
|
|
95
95
|
|
|
96
96
|
asTime (date) {
|
|
97
|
-
|
|
97
|
+
if (date === null) return '""'
|
|
98
98
|
if (date instanceof Date) {
|
|
99
|
-
return
|
|
99
|
+
return '"' + new Date(date.getTime() - (date.getTimezoneOffset() * 60000)).toISOString().slice(11, 19) + '"'
|
|
100
100
|
}
|
|
101
|
-
|
|
101
|
+
throw new Error(`The value "${date}" cannot be converted to a time.`)
|
|
102
102
|
}
|
|
103
103
|
|
|
104
104
|
asTimeNullable (date) {
|
package/test/basic.test.js
CHANGED
|
@@ -359,3 +359,16 @@ test('render a single quote as JSON', (t) => {
|
|
|
359
359
|
t.equal(output, JSON.stringify(toStringify))
|
|
360
360
|
t.ok(validate(JSON.parse(output)), 'valid schema')
|
|
361
361
|
})
|
|
362
|
+
|
|
363
|
+
test('returns JSON.stringify if schema type is boolean', t => {
|
|
364
|
+
t.plan(1)
|
|
365
|
+
|
|
366
|
+
const schema = {
|
|
367
|
+
type: 'array',
|
|
368
|
+
items: true
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
const array = [1, true, 'test']
|
|
372
|
+
const stringify = build(schema)
|
|
373
|
+
t.equal(stringify(array), JSON.stringify(array))
|
|
374
|
+
})
|
package/test/date.test.js
CHANGED
|
@@ -308,7 +308,7 @@ test('serializing null value', t => {
|
|
|
308
308
|
})
|
|
309
309
|
|
|
310
310
|
t.test('type::array', t => {
|
|
311
|
-
t.plan(
|
|
311
|
+
t.plan(6)
|
|
312
312
|
|
|
313
313
|
t.test('format::date-time', t => {
|
|
314
314
|
t.plan(2)
|
|
@@ -348,13 +348,13 @@ test('serializing null value', t => {
|
|
|
348
348
|
t.notOk(validate(JSON.parse(output)), 'an empty string is not a date format')
|
|
349
349
|
})
|
|
350
350
|
|
|
351
|
-
t.test('format::
|
|
351
|
+
t.test('format::date', t => {
|
|
352
352
|
t.plan(2)
|
|
353
353
|
|
|
354
354
|
const prop = {
|
|
355
355
|
updatedAt: {
|
|
356
356
|
type: ['string'],
|
|
357
|
-
format: '
|
|
357
|
+
format: 'date'
|
|
358
358
|
}
|
|
359
359
|
}
|
|
360
360
|
|
|
@@ -364,7 +364,66 @@ test('serializing null value', t => {
|
|
|
364
364
|
} = serialize(createSchema(prop), input)
|
|
365
365
|
|
|
366
366
|
t.equal(output, '{"updatedAt":""}')
|
|
367
|
-
t.notOk(validate(JSON.parse(output)), 'an empty string is not a
|
|
367
|
+
t.notOk(validate(JSON.parse(output)), 'an empty string is not a date format')
|
|
368
|
+
})
|
|
369
|
+
|
|
370
|
+
t.test('format::time, Date object', t => {
|
|
371
|
+
t.plan(1)
|
|
372
|
+
|
|
373
|
+
const schema = {
|
|
374
|
+
oneOf: [
|
|
375
|
+
{
|
|
376
|
+
type: 'object',
|
|
377
|
+
properties: {
|
|
378
|
+
updatedAt: {
|
|
379
|
+
type: ['string', 'number'],
|
|
380
|
+
format: 'time'
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
]
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
const date = new Date()
|
|
388
|
+
const input = { updatedAt: date }
|
|
389
|
+
const { output } = serialize(schema, input)
|
|
390
|
+
|
|
391
|
+
t.equal(output, JSON.stringify({ updatedAt: DateTime.fromJSDate(date).toFormat('HH:mm:ss') }))
|
|
392
|
+
})
|
|
393
|
+
|
|
394
|
+
t.test('format::time, Date object', t => {
|
|
395
|
+
t.plan(1)
|
|
396
|
+
|
|
397
|
+
const schema = {
|
|
398
|
+
oneOf: [
|
|
399
|
+
{
|
|
400
|
+
type: ['string', 'number'],
|
|
401
|
+
format: 'time'
|
|
402
|
+
}
|
|
403
|
+
]
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
const date = new Date()
|
|
407
|
+
const { output } = serialize(schema, date)
|
|
408
|
+
|
|
409
|
+
t.equal(output, `"${DateTime.fromJSDate(date).toFormat('HH:mm:ss')}"`)
|
|
410
|
+
})
|
|
411
|
+
|
|
412
|
+
t.test('format::time, Date object', t => {
|
|
413
|
+
t.plan(1)
|
|
414
|
+
|
|
415
|
+
const schema = {
|
|
416
|
+
oneOf: [
|
|
417
|
+
{
|
|
418
|
+
type: ['string', 'number'],
|
|
419
|
+
format: 'time'
|
|
420
|
+
}
|
|
421
|
+
]
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
const { output } = serialize(schema, 42)
|
|
425
|
+
|
|
426
|
+
t.equal(output, JSON.stringify(42))
|
|
368
427
|
})
|
|
369
428
|
})
|
|
370
429
|
|
|
@@ -429,3 +488,40 @@ test('serializing null value', t => {
|
|
|
429
488
|
})
|
|
430
489
|
})
|
|
431
490
|
})
|
|
491
|
+
|
|
492
|
+
test('Validate Date object as string type', (t) => {
|
|
493
|
+
t.plan(1)
|
|
494
|
+
|
|
495
|
+
const schema = {
|
|
496
|
+
oneOf: [
|
|
497
|
+
{ type: 'string' }
|
|
498
|
+
]
|
|
499
|
+
}
|
|
500
|
+
const toStringify = new Date()
|
|
501
|
+
|
|
502
|
+
const stringify = build(schema)
|
|
503
|
+
const output = stringify(toStringify)
|
|
504
|
+
|
|
505
|
+
t.equal(output, JSON.stringify(toStringify))
|
|
506
|
+
})
|
|
507
|
+
|
|
508
|
+
test('nullable date', (t) => {
|
|
509
|
+
t.plan(1)
|
|
510
|
+
|
|
511
|
+
const schema = {
|
|
512
|
+
anyOf: [
|
|
513
|
+
{
|
|
514
|
+
format: 'date',
|
|
515
|
+
type: 'string',
|
|
516
|
+
nullable: true
|
|
517
|
+
}
|
|
518
|
+
]
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
const stringify = build(schema)
|
|
522
|
+
|
|
523
|
+
const data = new Date()
|
|
524
|
+
const result = stringify(data)
|
|
525
|
+
|
|
526
|
+
t.same(result, `"${DateTime.fromJSDate(data).toISODate()}"`)
|
|
527
|
+
})
|