fast-json-stringify 5.0.5 → 5.2.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/.github/workflows/benchmark.yml +12 -12
- package/.github/workflows/ci.yml +2 -0
- package/benchmark/bench.js +54 -0
- package/index.js +85 -102
- package/package.json +6 -5
- package/ref-resolver.js +84 -0
- package/serializer.js +21 -12
- package/standalone.js +8 -8
- package/test/basic.test.js +13 -0
- package/test/const.test.js +235 -8
- package/test/date.test.js +100 -4
- package/test/debug-mode.test.js +10 -4
- package/test/nullable.test.js +97 -0
- package/test/ref.test.js +55 -0
- package/test/typesArray.test.js +29 -0
- package/validator.js +75 -0
- package/ajv.js +0 -37
|
@@ -11,15 +11,15 @@ jobs:
|
|
|
11
11
|
permissions:
|
|
12
12
|
contents: read
|
|
13
13
|
outputs:
|
|
14
|
-
PR-BENCH-12: ${{ steps.benchmark-pr.outputs.BENCH_RESULT12 }}
|
|
15
14
|
PR-BENCH-14: ${{ steps.benchmark-pr.outputs.BENCH_RESULT14 }}
|
|
16
15
|
PR-BENCH-16: ${{ steps.benchmark-pr.outputs.BENCH_RESULT16 }}
|
|
17
|
-
|
|
16
|
+
PR-BENCH-18: ${{ steps.benchmark-pr.outputs.BENCH_RESULT18 }}
|
|
18
17
|
MAIN-BENCH-14: ${{ steps.benchmark-main.outputs.BENCH_RESULT14 }}
|
|
19
18
|
MAIN-BENCH-16: ${{ steps.benchmark-main.outputs.BENCH_RESULT16 }}
|
|
19
|
+
MAIN-BENCH-18: ${{ steps.benchmark-main.outputs.BENCH_RESULT18 }}
|
|
20
20
|
strategy:
|
|
21
21
|
matrix:
|
|
22
|
-
node-version: [
|
|
22
|
+
node-version: [14, 16, 18]
|
|
23
23
|
steps:
|
|
24
24
|
- uses: actions/checkout@v3
|
|
25
25
|
with:
|
|
@@ -75,38 +75,38 @@ jobs:
|
|
|
75
75
|
with:
|
|
76
76
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
77
77
|
message: |
|
|
78
|
-
**Node**:
|
|
78
|
+
**Node**: 14
|
|
79
79
|
**PR**:
|
|
80
80
|
```
|
|
81
|
-
${{ needs.benchmark.outputs.PR-BENCH-
|
|
81
|
+
${{ needs.benchmark.outputs.PR-BENCH-14 }}
|
|
82
82
|
```
|
|
83
83
|
**MAIN**:
|
|
84
84
|
```
|
|
85
|
-
${{ needs.benchmark.outputs.MAIN-BENCH-
|
|
85
|
+
${{ needs.benchmark.outputs.MAIN-BENCH-14 }}
|
|
86
86
|
```
|
|
87
87
|
|
|
88
88
|
---
|
|
89
89
|
|
|
90
|
-
**Node**:
|
|
90
|
+
**Node**: 16
|
|
91
91
|
**PR**:
|
|
92
92
|
```
|
|
93
|
-
${{ needs.benchmark.outputs.PR-BENCH-
|
|
93
|
+
${{ needs.benchmark.outputs.PR-BENCH-16 }}
|
|
94
94
|
```
|
|
95
95
|
**MAIN**:
|
|
96
96
|
```
|
|
97
|
-
${{ needs.benchmark.outputs.MAIN-BENCH-
|
|
97
|
+
${{ needs.benchmark.outputs.MAIN-BENCH-16 }}
|
|
98
98
|
```
|
|
99
99
|
|
|
100
100
|
---
|
|
101
101
|
|
|
102
|
-
**Node**:
|
|
102
|
+
**Node**: 18
|
|
103
103
|
**PR**:
|
|
104
104
|
```
|
|
105
|
-
${{ needs.benchmark.outputs.PR-BENCH-
|
|
105
|
+
${{ needs.benchmark.outputs.PR-BENCH-18 }}
|
|
106
106
|
```
|
|
107
107
|
**MAIN**:
|
|
108
108
|
```
|
|
109
|
-
${{ needs.benchmark.outputs.MAIN-BENCH-
|
|
109
|
+
${{ needs.benchmark.outputs.MAIN-BENCH-18 }}
|
|
110
110
|
```
|
|
111
111
|
|
|
112
112
|
- uses: actions-ecosystem/action-remove-labels@v1
|
package/.github/workflows/ci.yml
CHANGED
package/benchmark/bench.js
CHANGED
|
@@ -213,6 +213,60 @@ const benchmarks = [
|
|
|
213
213
|
n5: 42,
|
|
214
214
|
b5: true
|
|
215
215
|
}
|
|
216
|
+
},
|
|
217
|
+
{
|
|
218
|
+
name: 'object with const string property',
|
|
219
|
+
schema: {
|
|
220
|
+
type: 'object',
|
|
221
|
+
properties: {
|
|
222
|
+
a: { const: 'const string' }
|
|
223
|
+
}
|
|
224
|
+
},
|
|
225
|
+
input: { a: 'const string' }
|
|
226
|
+
},
|
|
227
|
+
{
|
|
228
|
+
name: 'object with const number property',
|
|
229
|
+
schema: {
|
|
230
|
+
type: 'object',
|
|
231
|
+
properties: {
|
|
232
|
+
a: { const: 1 }
|
|
233
|
+
}
|
|
234
|
+
},
|
|
235
|
+
input: { a: 1 }
|
|
236
|
+
},
|
|
237
|
+
{
|
|
238
|
+
name: 'object with const bool property',
|
|
239
|
+
schema: {
|
|
240
|
+
type: 'object',
|
|
241
|
+
properties: {
|
|
242
|
+
a: { const: true }
|
|
243
|
+
}
|
|
244
|
+
},
|
|
245
|
+
input: { a: true }
|
|
246
|
+
},
|
|
247
|
+
{
|
|
248
|
+
name: 'object with const object property',
|
|
249
|
+
schema: {
|
|
250
|
+
type: 'object',
|
|
251
|
+
properties: {
|
|
252
|
+
foo: { const: { bar: 'baz' } }
|
|
253
|
+
}
|
|
254
|
+
},
|
|
255
|
+
input: {
|
|
256
|
+
foo: { bar: 'baz' }
|
|
257
|
+
}
|
|
258
|
+
},
|
|
259
|
+
{
|
|
260
|
+
name: 'object with const null property',
|
|
261
|
+
schema: {
|
|
262
|
+
type: 'object',
|
|
263
|
+
properties: {
|
|
264
|
+
foo: { const: null }
|
|
265
|
+
}
|
|
266
|
+
},
|
|
267
|
+
input: {
|
|
268
|
+
foo: null
|
|
269
|
+
}
|
|
216
270
|
}
|
|
217
271
|
]
|
|
218
272
|
|
package/index.js
CHANGED
|
@@ -2,14 +2,15 @@
|
|
|
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')
|
|
9
9
|
|
|
10
10
|
const validate = require('./schema-validator')
|
|
11
11
|
const Serializer = require('./serializer')
|
|
12
|
-
const
|
|
12
|
+
const Validator = require('./validator')
|
|
13
|
+
const RefResolver = require('./ref-resolver')
|
|
13
14
|
|
|
14
15
|
let largeArraySize = 2e4
|
|
15
16
|
let largeArrayMechanism = 'default'
|
|
@@ -55,22 +56,14 @@ function resolveRef (location, ref) {
|
|
|
55
56
|
}
|
|
56
57
|
|
|
57
58
|
const schemaId = ref.slice(0, hashIndex) || location.schemaId
|
|
58
|
-
const jsonPointer = ref.slice(hashIndex)
|
|
59
|
+
const jsonPointer = ref.slice(hashIndex) || '#'
|
|
59
60
|
|
|
60
|
-
const
|
|
61
|
+
const schema = refResolver.getSchema(schemaId, jsonPointer)
|
|
61
62
|
|
|
62
|
-
|
|
63
|
-
try {
|
|
64
|
-
ajvSchema = ajvInstance.getSchema(schemaRef)
|
|
65
|
-
} catch (error) {
|
|
63
|
+
if (schema === undefined) {
|
|
66
64
|
throw new Error(`Cannot find reference "${ref}"`)
|
|
67
65
|
}
|
|
68
66
|
|
|
69
|
-
if (ajvSchema === undefined) {
|
|
70
|
-
throw new Error(`Cannot find reference "${ref}"`)
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
const schema = ajvSchema.schema
|
|
74
67
|
if (schema.$ref !== undefined) {
|
|
75
68
|
return resolveRef({ schema, schemaId, jsonPointer }, schema.$ref)
|
|
76
69
|
}
|
|
@@ -82,41 +75,31 @@ const arrayItemsReferenceSerializersMap = new Map()
|
|
|
82
75
|
const objectReferenceSerializersMap = new Map()
|
|
83
76
|
|
|
84
77
|
let rootSchemaId = null
|
|
85
|
-
let
|
|
78
|
+
let refResolver = null
|
|
79
|
+
let validator = null
|
|
86
80
|
let contextFunctions = null
|
|
87
81
|
|
|
88
82
|
function build (schema, options) {
|
|
89
|
-
schema = clone(schema)
|
|
90
|
-
|
|
91
83
|
arrayItemsReferenceSerializersMap.clear()
|
|
92
84
|
objectReferenceSerializersMap.clear()
|
|
93
85
|
|
|
94
86
|
contextFunctions = []
|
|
95
87
|
options = options || {}
|
|
96
88
|
|
|
97
|
-
|
|
89
|
+
refResolver = new RefResolver()
|
|
90
|
+
validator = new Validator(options.ajv)
|
|
91
|
+
|
|
98
92
|
rootSchemaId = schema.$id || randomUUID()
|
|
99
93
|
|
|
100
94
|
isValidSchema(schema)
|
|
101
|
-
|
|
102
|
-
|
|
95
|
+
validator.addSchema(schema, rootSchemaId)
|
|
96
|
+
refResolver.addSchema(schema, rootSchemaId)
|
|
103
97
|
|
|
104
98
|
if (options.schema) {
|
|
105
|
-
const
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
isValidSchema(externalSchema, key)
|
|
110
|
-
extendDateTimeType(externalSchema)
|
|
111
|
-
|
|
112
|
-
let schemaKey = externalSchema.$id || key
|
|
113
|
-
if (externalSchema.$id !== undefined && externalSchema.$id[0] === '#') {
|
|
114
|
-
schemaKey = key + externalSchema.$id // relative URI
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
if (ajvInstance.getSchema(schemaKey) === undefined) {
|
|
118
|
-
ajvInstance.addSchema(externalSchema, schemaKey)
|
|
119
|
-
}
|
|
99
|
+
for (const key of Object.keys(options.schema)) {
|
|
100
|
+
isValidSchema(options.schema[key], key)
|
|
101
|
+
validator.addSchema(options.schema[key], key)
|
|
102
|
+
refResolver.addSchema(options.schema[key], key)
|
|
120
103
|
}
|
|
121
104
|
}
|
|
122
105
|
|
|
@@ -157,27 +140,28 @@ function build (schema, options) {
|
|
|
157
140
|
return main
|
|
158
141
|
`
|
|
159
142
|
|
|
160
|
-
const dependenciesName = ['
|
|
143
|
+
const dependenciesName = ['validator', 'serializer', contextFunctionCode]
|
|
161
144
|
|
|
162
145
|
if (options.debugMode) {
|
|
163
146
|
options.mode = 'debug'
|
|
164
147
|
}
|
|
165
148
|
|
|
166
149
|
if (options.mode === 'debug') {
|
|
167
|
-
return { code: dependenciesName.join('\n'), ajv:
|
|
150
|
+
return { code: dependenciesName.join('\n'), validator, ajv: validator.ajv }
|
|
168
151
|
}
|
|
169
152
|
|
|
170
153
|
if (options.mode === 'standalone') {
|
|
171
154
|
// lazy load
|
|
172
155
|
const buildStandaloneCode = require('./standalone')
|
|
173
|
-
return buildStandaloneCode(options,
|
|
156
|
+
return buildStandaloneCode(options, validator, contextFunctionCode)
|
|
174
157
|
}
|
|
175
158
|
|
|
176
159
|
/* eslint no-new-func: "off" */
|
|
177
|
-
const contextFunc = new Function('
|
|
178
|
-
const stringifyFunc = contextFunc(
|
|
160
|
+
const contextFunc = new Function('validator', 'serializer', contextFunctionCode)
|
|
161
|
+
const stringifyFunc = contextFunc(validator, serializer)
|
|
179
162
|
|
|
180
|
-
|
|
163
|
+
refResolver = null
|
|
164
|
+
validator = null
|
|
181
165
|
rootSchemaId = null
|
|
182
166
|
contextFunctions = null
|
|
183
167
|
arrayItemsReferenceSerializersMap.clear()
|
|
@@ -243,15 +227,6 @@ function inferTypeByKeyword (schema) {
|
|
|
243
227
|
return schema.type
|
|
244
228
|
}
|
|
245
229
|
|
|
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
230
|
function addPatternProperties (location) {
|
|
256
231
|
const schema = location.schema
|
|
257
232
|
const pp = schema.patternProperties
|
|
@@ -350,17 +325,16 @@ function buildCode (location) {
|
|
|
350
325
|
const propertiesLocation = mergeLocation(location, 'properties')
|
|
351
326
|
Object.keys(schema.properties || {}).forEach((key) => {
|
|
352
327
|
let propertyLocation = mergeLocation(propertiesLocation, key)
|
|
353
|
-
if (
|
|
354
|
-
propertyLocation = resolveRef(location,
|
|
355
|
-
schema.properties[key] = propertyLocation.schema
|
|
328
|
+
if (propertyLocation.$ref) {
|
|
329
|
+
propertyLocation = resolveRef(location, propertyLocation.$ref)
|
|
356
330
|
}
|
|
357
331
|
|
|
358
|
-
// Using obj['key'] !== undefined instead of obj.hasOwnProperty(prop) for perf reasons,
|
|
359
|
-
// see https://github.com/mcollina/fast-json-stringify/pull/3 for discussion.
|
|
360
|
-
|
|
361
332
|
const sanitized = JSON.stringify(key)
|
|
362
333
|
const asString = JSON.stringify(sanitized)
|
|
363
334
|
|
|
335
|
+
// Using obj['key'] !== undefined instead of obj.hasOwnProperty(prop) for perf reasons,
|
|
336
|
+
// see https://github.com/mcollina/fast-json-stringify/pull/3 for discussion.
|
|
337
|
+
|
|
364
338
|
code += `
|
|
365
339
|
if (obj[${sanitized}] !== undefined) {
|
|
366
340
|
${addComma}
|
|
@@ -369,7 +343,7 @@ function buildCode (location) {
|
|
|
369
343
|
|
|
370
344
|
code += buildValue(propertyLocation, `obj[${JSON.stringify(key)}]`)
|
|
371
345
|
|
|
372
|
-
const defaultValue = schema.
|
|
346
|
+
const defaultValue = propertyLocation.schema.default
|
|
373
347
|
if (defaultValue !== undefined) {
|
|
374
348
|
code += `
|
|
375
349
|
} else {
|
|
@@ -491,7 +465,8 @@ function mergeAllOfSchema (location, schema, mergedSchema) {
|
|
|
491
465
|
delete mergedSchema.allOf
|
|
492
466
|
|
|
493
467
|
mergedSchema.$id = `merged_${randomUUID()}`
|
|
494
|
-
|
|
468
|
+
validator.addSchema(mergedSchema)
|
|
469
|
+
refResolver.addSchema(mergedSchema)
|
|
495
470
|
location.schemaId = mergedSchema.$id
|
|
496
471
|
location.jsonPointer = '#'
|
|
497
472
|
}
|
|
@@ -520,7 +495,7 @@ function addIfThenElse (location) {
|
|
|
520
495
|
const ifSchemaRef = ifLocation.schemaId + ifLocation.jsonPointer
|
|
521
496
|
|
|
522
497
|
let code = `
|
|
523
|
-
if (
|
|
498
|
+
if (validator.validate("${ifSchemaRef}", obj)) {
|
|
524
499
|
`
|
|
525
500
|
|
|
526
501
|
const thenLocation = mergeLocation(location, 'then')
|
|
@@ -771,6 +746,10 @@ function generateFuncName () {
|
|
|
771
746
|
function buildValue (location, input) {
|
|
772
747
|
let schema = location.schema
|
|
773
748
|
|
|
749
|
+
if (typeof schema === 'boolean') {
|
|
750
|
+
return `json += JSON.stringify(${input})`
|
|
751
|
+
}
|
|
752
|
+
|
|
774
753
|
if (schema.$ref) {
|
|
775
754
|
location = resolveRef(location, schema.$ref)
|
|
776
755
|
schema = location.schema
|
|
@@ -791,19 +770,36 @@ function buildValue (location, input) {
|
|
|
791
770
|
}
|
|
792
771
|
|
|
793
772
|
const type = schema.type
|
|
794
|
-
const nullable = schema.nullable === true
|
|
773
|
+
const nullable = schema.nullable === true || (Array.isArray(type) && type.includes('null'))
|
|
795
774
|
|
|
796
775
|
let code = ''
|
|
797
776
|
let funcName
|
|
798
777
|
|
|
799
|
-
|
|
800
|
-
|
|
778
|
+
if ('const' in schema) {
|
|
779
|
+
if (nullable) {
|
|
801
780
|
code += `
|
|
802
|
-
json +=
|
|
781
|
+
json += ${input} === null ? 'null' : '${JSON.stringify(schema.const)}'
|
|
803
782
|
`
|
|
783
|
+
return code
|
|
784
|
+
}
|
|
785
|
+
code += `json += '${JSON.stringify(schema.const)}'`
|
|
786
|
+
return code
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
switch (type) {
|
|
790
|
+
case 'null':
|
|
791
|
+
code += 'json += serializer.asNull()'
|
|
804
792
|
break
|
|
805
793
|
case 'string': {
|
|
806
|
-
|
|
794
|
+
if (schema.format === 'date-time') {
|
|
795
|
+
funcName = nullable ? 'serializer.asDateTimeNullable.bind(serializer)' : 'serializer.asDateTime.bind(serializer)'
|
|
796
|
+
} else if (schema.format === 'date') {
|
|
797
|
+
funcName = nullable ? 'serializer.asDateNullable.bind(serializer)' : 'serializer.asDate.bind(serializer)'
|
|
798
|
+
} else if (schema.format === 'time') {
|
|
799
|
+
funcName = nullable ? 'serializer.asTimeNullable.bind(serializer)' : 'serializer.asTime.bind(serializer)'
|
|
800
|
+
} else {
|
|
801
|
+
funcName = nullable ? 'serializer.asStringNullable.bind(serializer)' : 'serializer.asString.bind(serializer)'
|
|
802
|
+
}
|
|
807
803
|
code += `json += ${funcName}(${input})`
|
|
808
804
|
break
|
|
809
805
|
}
|
|
@@ -828,11 +824,7 @@ function buildValue (location, input) {
|
|
|
828
824
|
code += `json += ${funcName}(${input})`
|
|
829
825
|
break
|
|
830
826
|
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) {
|
|
827
|
+
if (schema.anyOf || schema.oneOf) {
|
|
836
828
|
// beware: dereferenceOfRefs has side effects and changes schema.anyOf
|
|
837
829
|
const type = schema.anyOf ? 'anyOf' : 'oneOf'
|
|
838
830
|
const anyOfLocation = mergeLocation(location, type)
|
|
@@ -842,7 +834,7 @@ function buildValue (location, input) {
|
|
|
842
834
|
const schemaRef = optionLocation.schemaId + optionLocation.jsonPointer
|
|
843
835
|
const nestedResult = buildValue(optionLocation, input)
|
|
844
836
|
code += `
|
|
845
|
-
${index === 0 ? 'if' : 'else if'}(
|
|
837
|
+
${index === 0 ? 'if' : 'else if'}(validator.validate("${schemaRef}", ${input}))
|
|
846
838
|
${nestedResult}
|
|
847
839
|
`
|
|
848
840
|
}
|
|
@@ -854,19 +846,10 @@ function buildValue (location, input) {
|
|
|
854
846
|
code += `
|
|
855
847
|
json += JSON.stringify(${input})
|
|
856
848
|
`
|
|
857
|
-
} else
|
|
858
|
-
code += `
|
|
859
|
-
if(ajv.validate(${JSON.stringify(schema)}, ${input}))
|
|
860
|
-
json += '${JSON.stringify(schema.const)}'
|
|
861
|
-
else
|
|
862
|
-
throw new Error(\`Item $\{JSON.stringify(${input})} does not match schema definition.\`)
|
|
863
|
-
`
|
|
864
|
-
} else if (schema.type === undefined) {
|
|
849
|
+
} else {
|
|
865
850
|
code += `
|
|
866
851
|
json += JSON.stringify(${input})
|
|
867
852
|
`
|
|
868
|
-
} else {
|
|
869
|
-
throw new Error(`${schema.type} unsupported`)
|
|
870
853
|
}
|
|
871
854
|
break
|
|
872
855
|
default:
|
|
@@ -890,7 +873,18 @@ function buildValue (location, input) {
|
|
|
890
873
|
switch (type) {
|
|
891
874
|
case 'string': {
|
|
892
875
|
code += `
|
|
893
|
-
${statement}(
|
|
876
|
+
${statement}(
|
|
877
|
+
typeof ${input} === "string" ||
|
|
878
|
+
${input} === null ||
|
|
879
|
+
${input} instanceof Date ||
|
|
880
|
+
${input} instanceof RegExp ||
|
|
881
|
+
(
|
|
882
|
+
typeof ${input} === "object" &&
|
|
883
|
+
typeof ${input}.toString === "function" &&
|
|
884
|
+
${input}.toString !== Object.prototype.toString &&
|
|
885
|
+
!(${input} instanceof Date)
|
|
886
|
+
)
|
|
887
|
+
)
|
|
894
888
|
${nestedResult}
|
|
895
889
|
`
|
|
896
890
|
break
|
|
@@ -909,6 +903,13 @@ function buildValue (location, input) {
|
|
|
909
903
|
`
|
|
910
904
|
break
|
|
911
905
|
}
|
|
906
|
+
case 'object': {
|
|
907
|
+
code += `
|
|
908
|
+
${statement}(typeof ${input} === "object" || ${input} === null)
|
|
909
|
+
${nestedResult}
|
|
910
|
+
`
|
|
911
|
+
break
|
|
912
|
+
}
|
|
912
913
|
default: {
|
|
913
914
|
code += `
|
|
914
915
|
${statement}(typeof ${input} === "${type}" || ${input} === null)
|
|
@@ -935,28 +936,10 @@ function buildValue (location, input) {
|
|
|
935
936
|
return code
|
|
936
937
|
}
|
|
937
938
|
|
|
938
|
-
// 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 fjs_date_type.
|
|
940
|
-
// (see https://github.com/fastify/fast-json-stringify/pull/441)
|
|
941
|
-
function extendDateTimeType (schema) {
|
|
942
|
-
if (schema === null) return
|
|
943
|
-
|
|
944
|
-
if (schema.type === 'string' && ['date-time', 'date', 'time'].includes(schema.format)) {
|
|
945
|
-
schema.fjs_date_type = schema.format
|
|
946
|
-
delete schema.type
|
|
947
|
-
delete schema.format
|
|
948
|
-
}
|
|
949
|
-
for (const property in schema) {
|
|
950
|
-
if (typeof schema[property] === 'object') {
|
|
951
|
-
extendDateTimeType(schema[property])
|
|
952
|
-
}
|
|
953
|
-
}
|
|
954
|
-
}
|
|
955
|
-
|
|
956
939
|
function isEmpty (schema) {
|
|
957
940
|
// eslint-disable-next-line
|
|
958
941
|
for (var key in schema) {
|
|
959
|
-
if (
|
|
942
|
+
if (Object.prototype.hasOwnProperty.call(schema, key) && schema[key] !== undefined) {
|
|
960
943
|
return false
|
|
961
944
|
}
|
|
962
945
|
}
|
|
@@ -967,9 +950,9 @@ module.exports = build
|
|
|
967
950
|
|
|
968
951
|
module.exports.validLargeArrayMechanisms = validLargeArrayMechanisms
|
|
969
952
|
|
|
970
|
-
module.exports.restore = function ({ code,
|
|
953
|
+
module.exports.restore = function ({ code, validator }) {
|
|
971
954
|
const serializer = new Serializer()
|
|
972
955
|
// eslint-disable-next-line
|
|
973
|
-
return (Function.apply(null, ['
|
|
974
|
-
.apply(null, [
|
|
956
|
+
return (Function.apply(null, ['validator', 'serializer', code])
|
|
957
|
+
.apply(null, [validator, serializer]))
|
|
975
958
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fast-json-stringify",
|
|
3
|
-
"version": "5.0
|
|
3
|
+
"version": "5.2.0",
|
|
4
4
|
"description": "Stringify your JSON at max speed",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"types": "index.d.ts",
|
|
@@ -33,13 +33,13 @@
|
|
|
33
33
|
},
|
|
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.24.9",
|
|
37
38
|
"benchmark": "^2.1.4",
|
|
38
39
|
"cli-select": "^1.1.2",
|
|
39
40
|
"compile-json-stringify": "^0.1.2",
|
|
40
41
|
"is-my-json-valid": "^2.20.0",
|
|
41
|
-
"luxon": "^
|
|
42
|
-
"pre-commit": "^1.2.2",
|
|
42
|
+
"luxon": "^3.0.1",
|
|
43
43
|
"proxyquire": "^2.1.3",
|
|
44
44
|
"semver": "^7.1.0",
|
|
45
45
|
"simple-git": "^3.7.1",
|
|
@@ -49,9 +49,10 @@
|
|
|
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
|
-
"
|
|
55
|
+
"fast-deep-equal": "^3.1.3",
|
|
55
56
|
"fast-uri": "^2.1.0",
|
|
56
57
|
"rfdc": "^1.2.0"
|
|
57
58
|
},
|
package/ref-resolver.js
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const deepEqual = require('fast-deep-equal')
|
|
4
|
+
|
|
5
|
+
class RefResolver {
|
|
6
|
+
constructor () {
|
|
7
|
+
this.schemas = {}
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
addSchema (schema, schemaId) {
|
|
11
|
+
if (schema.$id !== undefined && schema.$id.charAt(0) !== '#') {
|
|
12
|
+
schemaId = schema.$id
|
|
13
|
+
}
|
|
14
|
+
if (this.getSchema(schemaId) === undefined) {
|
|
15
|
+
this.insertSchemaBySchemaId(schema, schemaId)
|
|
16
|
+
this.insertSchemaSubschemas(schema, schemaId)
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
getSchema (schemaId, jsonPointer = '#') {
|
|
21
|
+
const schema = this.schemas[schemaId]
|
|
22
|
+
if (schema === undefined) {
|
|
23
|
+
return undefined
|
|
24
|
+
}
|
|
25
|
+
if (schema.anchors[jsonPointer] !== undefined) {
|
|
26
|
+
return schema.anchors[jsonPointer]
|
|
27
|
+
}
|
|
28
|
+
return getDataByJSONPointer(schema.schema, jsonPointer)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
insertSchemaBySchemaId (schema, schemaId) {
|
|
32
|
+
if (
|
|
33
|
+
this.schemas[schemaId] !== undefined &&
|
|
34
|
+
!deepEqual(schema, this.schemas[schemaId].schema)
|
|
35
|
+
) {
|
|
36
|
+
throw new Error(`There is already another schema with id ${schemaId}`)
|
|
37
|
+
}
|
|
38
|
+
this.schemas[schemaId] = { schema, anchors: {} }
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
insertSchemaByAnchor (schema, schemaId, anchor) {
|
|
42
|
+
const { anchors } = this.schemas[schemaId]
|
|
43
|
+
if (
|
|
44
|
+
anchors[anchor] !== undefined &&
|
|
45
|
+
!deepEqual(schema, anchors[anchor])
|
|
46
|
+
) {
|
|
47
|
+
throw new Error(`There is already another schema with id ${schemaId}#${anchor}`)
|
|
48
|
+
}
|
|
49
|
+
anchors[anchor] = schema
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
insertSchemaSubschemas (schema, rootSchemaId) {
|
|
53
|
+
const schemaId = schema.$id
|
|
54
|
+
if (schemaId !== undefined && typeof schemaId === 'string') {
|
|
55
|
+
if (schemaId.charAt(0) === '#') {
|
|
56
|
+
this.insertSchemaByAnchor(schema, rootSchemaId, schemaId)
|
|
57
|
+
} else {
|
|
58
|
+
this.insertSchemaBySchemaId(schema, schemaId)
|
|
59
|
+
rootSchemaId = schemaId
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
for (const key in schema) {
|
|
64
|
+
if (typeof schema[key] === 'object' && schema[key] !== null) {
|
|
65
|
+
this.insertSchemaSubschemas(schema[key], rootSchemaId)
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function getDataByJSONPointer (data, jsonPointer) {
|
|
72
|
+
const parts = jsonPointer.split('/')
|
|
73
|
+
let current = data
|
|
74
|
+
for (const part of parts) {
|
|
75
|
+
if (part === '' || part === '#') continue
|
|
76
|
+
if (typeof current !== 'object' || current === null) {
|
|
77
|
+
return undefined
|
|
78
|
+
}
|
|
79
|
+
current = current[part]
|
|
80
|
+
}
|
|
81
|
+
return current
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
module.exports = RefResolver
|
package/serializer.js
CHANGED
|
@@ -69,24 +69,30 @@ 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
|
+
}
|
|
77
|
+
if (typeof date === 'string') {
|
|
78
|
+
return '"' + date + '"'
|
|
76
79
|
}
|
|
77
|
-
|
|
80
|
+
throw new Error(`The value "${date}" cannot be converted to a date-time.`)
|
|
78
81
|
}
|
|
79
82
|
|
|
80
|
-
|
|
81
|
-
return date === null ? 'null' : this.
|
|
83
|
+
asDateTimeNullable (date) {
|
|
84
|
+
return date === null ? 'null' : this.asDateTime(date)
|
|
82
85
|
}
|
|
83
86
|
|
|
84
87
|
asDate (date) {
|
|
85
|
-
|
|
88
|
+
if (date === null) return '""'
|
|
86
89
|
if (date instanceof Date) {
|
|
87
|
-
return
|
|
90
|
+
return '"' + new Date(date.getTime() - (date.getTimezoneOffset() * 60000)).toISOString().slice(0, 10) + '"'
|
|
88
91
|
}
|
|
89
|
-
|
|
92
|
+
if (typeof date === 'string') {
|
|
93
|
+
return '"' + date + '"'
|
|
94
|
+
}
|
|
95
|
+
throw new Error(`The value "${date}" cannot be converted to a date.`)
|
|
90
96
|
}
|
|
91
97
|
|
|
92
98
|
asDateNullable (date) {
|
|
@@ -94,11 +100,14 @@ module.exports = class Serializer {
|
|
|
94
100
|
}
|
|
95
101
|
|
|
96
102
|
asTime (date) {
|
|
97
|
-
|
|
103
|
+
if (date === null) return '""'
|
|
98
104
|
if (date instanceof Date) {
|
|
99
|
-
return
|
|
105
|
+
return '"' + new Date(date.getTime() - (date.getTimezoneOffset() * 60000)).toISOString().slice(11, 19) + '"'
|
|
106
|
+
}
|
|
107
|
+
if (typeof date === 'string') {
|
|
108
|
+
return '"' + date + '"'
|
|
100
109
|
}
|
|
101
|
-
|
|
110
|
+
throw new Error(`The value "${date}" cannot be converted to a time.`)
|
|
102
111
|
}
|
|
103
112
|
|
|
104
113
|
asTimeNullable (date) {
|