fast-json-stringify 5.2.0 → 5.4.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/ci.yml +1 -0
- package/.taprc +7 -1
- package/{build-schema-validator.js → build/build-schema-validator.js} +1 -1
- package/{example.js → examples/example.js} +1 -1
- package/{server.js → examples/server.js} +1 -1
- package/index.d.ts +9 -8
- package/index.js +316 -375
- package/lib/location.js +55 -0
- package/{ref-resolver.js → lib/ref-resolver.js} +0 -0
- package/{schema-validator.js → lib/schema-validator.js} +0 -0
- package/{serializer.js → lib/serializer.js} +0 -36
- package/{standalone.js → lib/standalone.js} +12 -11
- package/{validator.js → lib/validator.js} +0 -0
- package/package.json +10 -6
- package/test/allof.test.js +45 -0
- package/test/anyof.test.js +20 -0
- package/test/array.test.js +43 -0
- package/test/date.test.js +19 -0
- package/test/debug-mode.test.js +18 -5
- package/test/if-then-else.test.js +50 -0
- package/test/ref.test.js +24 -0
- package/test/types/schema-inference.test-d.ts +48 -0
package/index.js
CHANGED
|
@@ -4,13 +4,13 @@
|
|
|
4
4
|
|
|
5
5
|
const merge = require('@fastify/deepmerge')()
|
|
6
6
|
const clone = require('rfdc')({ proto: true })
|
|
7
|
-
const fjsCloned = Symbol('fast-json-stringify.cloned')
|
|
8
7
|
const { randomUUID } = require('crypto')
|
|
9
8
|
|
|
10
|
-
const validate = require('./schema-validator')
|
|
11
|
-
const Serializer = require('./serializer')
|
|
12
|
-
const Validator = require('./validator')
|
|
13
|
-
const RefResolver = require('./ref-resolver')
|
|
9
|
+
const validate = require('./lib/schema-validator')
|
|
10
|
+
const Serializer = require('./lib/serializer')
|
|
11
|
+
const Validator = require('./lib/validator')
|
|
12
|
+
const RefResolver = require('./lib/ref-resolver')
|
|
13
|
+
const Location = require('./lib/location')
|
|
14
14
|
|
|
15
15
|
let largeArraySize = 2e4
|
|
16
16
|
let largeArrayMechanism = 'default'
|
|
@@ -41,21 +41,13 @@ function isValidSchema (schema, name) {
|
|
|
41
41
|
}
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
function mergeLocation (location, key) {
|
|
45
|
-
return {
|
|
46
|
-
schema: location.schema[key],
|
|
47
|
-
schemaId: location.schemaId,
|
|
48
|
-
jsonPointer: location.jsonPointer + '/' + key
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
44
|
function resolveRef (location, ref) {
|
|
53
45
|
let hashIndex = ref.indexOf('#')
|
|
54
46
|
if (hashIndex === -1) {
|
|
55
47
|
hashIndex = ref.length
|
|
56
48
|
}
|
|
57
49
|
|
|
58
|
-
const schemaId = ref.slice(0, hashIndex) || location.
|
|
50
|
+
const schemaId = ref.slice(0, hashIndex) || location.getOriginSchemaId()
|
|
59
51
|
const jsonPointer = ref.slice(hashIndex) || '#'
|
|
60
52
|
|
|
61
53
|
const schema = refResolver.getSchema(schemaId, jsonPointer)
|
|
@@ -64,41 +56,42 @@ function resolveRef (location, ref) {
|
|
|
64
56
|
throw new Error(`Cannot find reference "${ref}"`)
|
|
65
57
|
}
|
|
66
58
|
|
|
59
|
+
if (location.isValidated) {
|
|
60
|
+
validatorSchemasIds.add(schemaId)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const newLocation = new Location(schema, schemaId, jsonPointer, location.isValidated)
|
|
67
64
|
if (schema.$ref !== undefined) {
|
|
68
|
-
return resolveRef(
|
|
65
|
+
return resolveRef(newLocation, schema.$ref)
|
|
69
66
|
}
|
|
70
67
|
|
|
71
|
-
return
|
|
68
|
+
return newLocation
|
|
72
69
|
}
|
|
73
70
|
|
|
74
|
-
const
|
|
75
|
-
const objectReferenceSerializersMap = new Map()
|
|
71
|
+
const contextFunctionsNamesBySchema = new Map()
|
|
76
72
|
|
|
77
73
|
let rootSchemaId = null
|
|
78
74
|
let refResolver = null
|
|
79
|
-
let validator = null
|
|
80
75
|
let contextFunctions = null
|
|
76
|
+
let validatorSchemasIds = null
|
|
81
77
|
|
|
82
78
|
function build (schema, options) {
|
|
83
|
-
|
|
84
|
-
objectReferenceSerializersMap.clear()
|
|
79
|
+
contextFunctionsNamesBySchema.clear()
|
|
85
80
|
|
|
86
81
|
contextFunctions = []
|
|
82
|
+
validatorSchemasIds = new Set()
|
|
87
83
|
options = options || {}
|
|
88
84
|
|
|
89
85
|
refResolver = new RefResolver()
|
|
90
|
-
validator = new Validator(options.ajv)
|
|
91
86
|
|
|
92
87
|
rootSchemaId = schema.$id || randomUUID()
|
|
93
88
|
|
|
94
89
|
isValidSchema(schema)
|
|
95
|
-
validator.addSchema(schema, rootSchemaId)
|
|
96
90
|
refResolver.addSchema(schema, rootSchemaId)
|
|
97
91
|
|
|
98
92
|
if (options.schema) {
|
|
99
93
|
for (const key of Object.keys(options.schema)) {
|
|
100
94
|
isValidSchema(options.schema[key], key)
|
|
101
|
-
validator.addSchema(options.schema[key], key)
|
|
102
95
|
refResolver.addSchema(options.schema[key], key)
|
|
103
96
|
}
|
|
104
97
|
}
|
|
@@ -113,7 +106,7 @@ function build (schema, options) {
|
|
|
113
106
|
if (validLargeArrayMechanisms.includes(options.largeArrayMechanism)) {
|
|
114
107
|
largeArrayMechanism = options.largeArrayMechanism
|
|
115
108
|
} else {
|
|
116
|
-
throw new Error(`Unsupported large array mechanism ${options.
|
|
109
|
+
throw new Error(`Unsupported large array mechanism ${options.largeArrayMechanism}`)
|
|
117
110
|
}
|
|
118
111
|
}
|
|
119
112
|
|
|
@@ -125,9 +118,7 @@ function build (schema, options) {
|
|
|
125
118
|
}
|
|
126
119
|
}
|
|
127
120
|
|
|
128
|
-
const
|
|
129
|
-
|
|
130
|
-
const location = { schema, schemaId: rootSchemaId, jsonPointer: '#' }
|
|
121
|
+
const location = new Location(schema, rootSchemaId)
|
|
131
122
|
const code = buildValue(location, 'input')
|
|
132
123
|
|
|
133
124
|
const contextFunctionCode = `
|
|
@@ -138,7 +129,15 @@ function build (schema, options) {
|
|
|
138
129
|
}
|
|
139
130
|
${contextFunctions.join('\n')}
|
|
140
131
|
return main
|
|
141
|
-
|
|
132
|
+
`
|
|
133
|
+
|
|
134
|
+
const serializer = new Serializer(options)
|
|
135
|
+
const validator = new Validator(options.ajv)
|
|
136
|
+
|
|
137
|
+
for (const schemaId of validatorSchemasIds) {
|
|
138
|
+
const schema = refResolver.getSchema(schemaId)
|
|
139
|
+
validator.addSchema(schema, schemaId)
|
|
140
|
+
}
|
|
142
141
|
|
|
143
142
|
const dependenciesName = ['validator', 'serializer', contextFunctionCode]
|
|
144
143
|
|
|
@@ -147,13 +146,19 @@ function build (schema, options) {
|
|
|
147
146
|
}
|
|
148
147
|
|
|
149
148
|
if (options.mode === 'debug') {
|
|
150
|
-
return {
|
|
149
|
+
return {
|
|
150
|
+
validator,
|
|
151
|
+
serializer,
|
|
152
|
+
code: dependenciesName.join('\n'),
|
|
153
|
+
ajv: validator.ajv
|
|
154
|
+
}
|
|
151
155
|
}
|
|
152
156
|
|
|
153
157
|
if (options.mode === 'standalone') {
|
|
154
158
|
// lazy load
|
|
155
|
-
const
|
|
156
|
-
|
|
159
|
+
const isValidatorUsed = validatorSchemasIds.size > 0
|
|
160
|
+
const buildStandaloneCode = require('./lib/standalone')
|
|
161
|
+
return buildStandaloneCode(options, validator, isValidatorUsed, contextFunctionCode)
|
|
157
162
|
}
|
|
158
163
|
|
|
159
164
|
/* eslint no-new-func: "off" */
|
|
@@ -161,11 +166,10 @@ function build (schema, options) {
|
|
|
161
166
|
const stringifyFunc = contextFunc(validator, serializer)
|
|
162
167
|
|
|
163
168
|
refResolver = null
|
|
164
|
-
validator = null
|
|
165
169
|
rootSchemaId = null
|
|
166
170
|
contextFunctions = null
|
|
167
|
-
|
|
168
|
-
|
|
171
|
+
validatorSchemasIds = null
|
|
172
|
+
contextFunctionsNamesBySchema.clear()
|
|
169
173
|
|
|
170
174
|
return stringifyFunc
|
|
171
175
|
}
|
|
@@ -227,106 +231,82 @@ function inferTypeByKeyword (schema) {
|
|
|
227
231
|
return schema.type
|
|
228
232
|
}
|
|
229
233
|
|
|
230
|
-
function
|
|
234
|
+
function buildExtraObjectPropertiesSerializer (location) {
|
|
231
235
|
const schema = location.schema
|
|
232
|
-
const
|
|
236
|
+
const propertiesKeys = Object.keys(schema.properties || {})
|
|
237
|
+
|
|
233
238
|
let code = `
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
239
|
+
const propertiesKeys = ${JSON.stringify(propertiesKeys)}
|
|
240
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
241
|
+
if (
|
|
242
|
+
propertiesKeys.includes(key) ||
|
|
243
|
+
value === undefined ||
|
|
244
|
+
typeof value === 'function' ||
|
|
245
|
+
typeof value === 'symbol'
|
|
246
|
+
) continue
|
|
238
247
|
`
|
|
239
248
|
|
|
240
|
-
const patternPropertiesLocation =
|
|
241
|
-
|
|
242
|
-
let ppLocation = mergeLocation(patternPropertiesLocation, regex)
|
|
243
|
-
if (pp[regex].$ref) {
|
|
244
|
-
ppLocation = resolveRef(ppLocation, pp[regex].$ref)
|
|
245
|
-
pp[regex] = ppLocation.schema
|
|
246
|
-
}
|
|
249
|
+
const patternPropertiesLocation = location.getPropertyLocation('patternProperties')
|
|
250
|
+
const patternPropertiesSchema = patternPropertiesLocation.schema
|
|
247
251
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
throw new Error(`${err.message}. Found at ${regex} matching ${JSON.stringify(pp[regex])}`)
|
|
252
|
-
}
|
|
252
|
+
if (patternPropertiesSchema !== undefined) {
|
|
253
|
+
for (const propertyKey in patternPropertiesSchema) {
|
|
254
|
+
const propertyLocation = patternPropertiesLocation.getPropertyLocation(propertyKey)
|
|
253
255
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
${valueCode}
|
|
260
|
-
continue
|
|
256
|
+
try {
|
|
257
|
+
RegExp(propertyKey)
|
|
258
|
+
} catch (err) {
|
|
259
|
+
const jsonPointer = propertyLocation.getSchemaRef()
|
|
260
|
+
throw new Error(`${err.message}. Invalid pattern property regexp key ${propertyKey} at ${jsonPointer}`)
|
|
261
261
|
}
|
|
262
|
-
`
|
|
263
|
-
})
|
|
264
|
-
if (schema.additionalProperties) {
|
|
265
|
-
code += additionalProperty(location)
|
|
266
|
-
}
|
|
267
262
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
`
|
|
271
|
-
return code
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
function additionalProperty (location) {
|
|
275
|
-
const ap = location.schema.additionalProperties
|
|
276
|
-
let code = ''
|
|
277
|
-
if (ap === true) {
|
|
278
|
-
code += `
|
|
279
|
-
if (obj[keys[i]] !== undefined && typeof obj[keys[i]] !== 'function' && typeof obj[keys[i]] !== 'symbol') {
|
|
263
|
+
code += `
|
|
264
|
+
if (/${propertyKey.replace(/\\*\//g, '\\/')}/.test(key)) {
|
|
280
265
|
${addComma}
|
|
281
|
-
json += serializer.asString(
|
|
266
|
+
json += serializer.asString(key) + ':'
|
|
267
|
+
${buildValue(propertyLocation, 'value')}
|
|
268
|
+
continue
|
|
282
269
|
}
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
return code
|
|
270
|
+
`
|
|
271
|
+
}
|
|
286
272
|
}
|
|
287
273
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
apLocation = resolveRef(apLocation, apLocation.schema.$ref)
|
|
291
|
-
}
|
|
274
|
+
const additionalPropertiesLocation = location.getPropertyLocation('additionalProperties')
|
|
275
|
+
const additionalPropertiesSchema = additionalPropertiesLocation.schema
|
|
292
276
|
|
|
293
|
-
|
|
277
|
+
if (additionalPropertiesSchema !== undefined) {
|
|
278
|
+
if (additionalPropertiesSchema === true) {
|
|
279
|
+
code += `
|
|
280
|
+
${addComma}
|
|
281
|
+
json += serializer.asString(key) + ':' + JSON.stringify(value)
|
|
282
|
+
`
|
|
283
|
+
} else {
|
|
284
|
+
const propertyLocation = location.getPropertyLocation('additionalProperties')
|
|
285
|
+
code += `
|
|
286
|
+
${addComma}
|
|
287
|
+
json += serializer.asString(key) + ':'
|
|
288
|
+
${buildValue(propertyLocation, 'value')}
|
|
289
|
+
`
|
|
290
|
+
}
|
|
291
|
+
}
|
|
294
292
|
|
|
295
293
|
code += `
|
|
296
|
-
|
|
297
|
-
json += serializer.asString(keys[i]) + ':'
|
|
298
|
-
${valueCode}
|
|
294
|
+
}
|
|
299
295
|
`
|
|
300
|
-
|
|
301
296
|
return code
|
|
302
297
|
}
|
|
303
298
|
|
|
304
|
-
function
|
|
305
|
-
return `
|
|
306
|
-
var properties = ${JSON.stringify(location.schema.properties)} || {}
|
|
307
|
-
var keys = Object.keys(obj)
|
|
308
|
-
for (var i = 0; i < keys.length; i++) {
|
|
309
|
-
if (properties[keys[i]]) continue
|
|
310
|
-
${additionalProperty(location)}
|
|
311
|
-
}
|
|
312
|
-
`
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
function buildCode (location) {
|
|
316
|
-
if (location.schema.$ref) {
|
|
317
|
-
location = resolveRef(location, location.schema.$ref)
|
|
318
|
-
}
|
|
319
|
-
|
|
299
|
+
function buildInnerObject (location) {
|
|
320
300
|
const schema = location.schema
|
|
321
301
|
const required = schema.required || []
|
|
322
302
|
|
|
323
303
|
let code = ''
|
|
324
304
|
|
|
325
|
-
const propertiesLocation =
|
|
305
|
+
const propertiesLocation = location.getPropertyLocation('properties')
|
|
326
306
|
Object.keys(schema.properties || {}).forEach((key) => {
|
|
327
|
-
let propertyLocation =
|
|
328
|
-
if (propertyLocation.$ref) {
|
|
329
|
-
propertyLocation = resolveRef(location, propertyLocation.$ref)
|
|
307
|
+
let propertyLocation = propertiesLocation.getPropertyLocation(key)
|
|
308
|
+
if (propertyLocation.schema.$ref) {
|
|
309
|
+
propertyLocation = resolveRef(location, propertyLocation.schema.$ref)
|
|
330
310
|
}
|
|
331
311
|
|
|
332
312
|
const sanitized = JSON.stringify(key)
|
|
@@ -367,17 +347,21 @@ function buildCode (location) {
|
|
|
367
347
|
code += `if (obj['${requiredProperty}'] === undefined) throw new Error('"${requiredProperty}" is required!')\n`
|
|
368
348
|
}
|
|
369
349
|
|
|
350
|
+
if (schema.patternProperties || schema.additionalProperties) {
|
|
351
|
+
code += buildExtraObjectPropertiesSerializer(location)
|
|
352
|
+
}
|
|
353
|
+
|
|
370
354
|
return code
|
|
371
355
|
}
|
|
372
356
|
|
|
373
357
|
function mergeAllOfSchema (location, schema, mergedSchema) {
|
|
374
|
-
const allOfLocation =
|
|
358
|
+
const allOfLocation = location.getPropertyLocation('allOf')
|
|
375
359
|
|
|
376
360
|
for (let i = 0; i < schema.allOf.length; i++) {
|
|
377
361
|
let allOfSchema = schema.allOf[i]
|
|
378
362
|
|
|
379
363
|
if (allOfSchema.$ref) {
|
|
380
|
-
const allOfSchemaLocation =
|
|
364
|
+
const allOfSchemaLocation = allOfLocation.getPropertyLocation(i)
|
|
381
365
|
allOfSchema = resolveRef(allOfSchemaLocation, allOfSchema.$ref).schema
|
|
382
366
|
}
|
|
383
367
|
|
|
@@ -465,24 +449,14 @@ function mergeAllOfSchema (location, schema, mergedSchema) {
|
|
|
465
449
|
delete mergedSchema.allOf
|
|
466
450
|
|
|
467
451
|
mergedSchema.$id = `merged_${randomUUID()}`
|
|
468
|
-
validator.addSchema(mergedSchema)
|
|
469
452
|
refResolver.addSchema(mergedSchema)
|
|
470
|
-
location.
|
|
471
|
-
location.jsonPointer = '#'
|
|
453
|
+
location.addMergedSchema(mergedSchema, mergedSchema.$id)
|
|
472
454
|
}
|
|
473
455
|
|
|
474
|
-
function
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
if (schema.patternProperties) {
|
|
478
|
-
code += addPatternProperties(location)
|
|
479
|
-
} else if (schema.additionalProperties && !schema.patternProperties) {
|
|
480
|
-
code += addAdditionalProperties(location)
|
|
481
|
-
}
|
|
482
|
-
return code
|
|
483
|
-
}
|
|
456
|
+
function addIfThenElse (location, input) {
|
|
457
|
+
location.isValidated = true
|
|
458
|
+
validatorSchemasIds.add(location.getSchemaId())
|
|
484
459
|
|
|
485
|
-
function addIfThenElse (location) {
|
|
486
460
|
const schema = merge({}, location.schema)
|
|
487
461
|
const thenSchema = schema.then
|
|
488
462
|
const elseSchema = schema.else || { additionalProperties: true }
|
|
@@ -491,41 +465,22 @@ function addIfThenElse (location) {
|
|
|
491
465
|
delete schema.then
|
|
492
466
|
delete schema.else
|
|
493
467
|
|
|
494
|
-
const ifLocation =
|
|
495
|
-
const ifSchemaRef = ifLocation.
|
|
496
|
-
|
|
497
|
-
let code = `
|
|
498
|
-
if (validator.validate("${ifSchemaRef}", obj)) {
|
|
499
|
-
`
|
|
468
|
+
const ifLocation = location.getPropertyLocation('if')
|
|
469
|
+
const ifSchemaRef = ifLocation.getSchemaRef()
|
|
500
470
|
|
|
501
|
-
const thenLocation =
|
|
471
|
+
const thenLocation = location.getPropertyLocation('then')
|
|
502
472
|
thenLocation.schema = merge(schema, thenSchema)
|
|
503
473
|
|
|
504
|
-
|
|
505
|
-
code += addIfThenElse(thenLocation)
|
|
506
|
-
} else {
|
|
507
|
-
code += buildInnerObject(thenLocation)
|
|
508
|
-
}
|
|
509
|
-
code += `
|
|
510
|
-
}
|
|
511
|
-
`
|
|
512
|
-
|
|
513
|
-
const elseLocation = mergeLocation(location, 'else')
|
|
474
|
+
const elseLocation = location.getPropertyLocation('else')
|
|
514
475
|
elseLocation.schema = merge(schema, elseSchema)
|
|
515
476
|
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
code += buildInnerObject(elseLocation)
|
|
524
|
-
}
|
|
525
|
-
code += `
|
|
526
|
-
}
|
|
527
|
-
`
|
|
528
|
-
return code
|
|
477
|
+
return `
|
|
478
|
+
if (validator.validate("${ifSchemaRef}", ${input})) {
|
|
479
|
+
${buildValue(thenLocation, input)}
|
|
480
|
+
} else {
|
|
481
|
+
${buildValue(elseLocation, input)}
|
|
482
|
+
}
|
|
483
|
+
`
|
|
529
484
|
}
|
|
530
485
|
|
|
531
486
|
function toJSON (variableName) {
|
|
@@ -538,25 +493,22 @@ function toJSON (variableName) {
|
|
|
538
493
|
function buildObject (location) {
|
|
539
494
|
const schema = location.schema
|
|
540
495
|
|
|
541
|
-
if (
|
|
542
|
-
return
|
|
496
|
+
if (contextFunctionsNamesBySchema.has(schema)) {
|
|
497
|
+
return contextFunctionsNamesBySchema.get(schema)
|
|
543
498
|
}
|
|
544
499
|
|
|
545
500
|
const functionName = generateFuncName()
|
|
546
|
-
|
|
501
|
+
contextFunctionsNamesBySchema.set(schema, functionName)
|
|
502
|
+
|
|
503
|
+
let schemaRef = location.getSchemaRef()
|
|
504
|
+
if (schemaRef.startsWith(rootSchemaId)) {
|
|
505
|
+
schemaRef = schemaRef.replace(rootSchemaId, '')
|
|
506
|
+
}
|
|
547
507
|
|
|
548
|
-
const schemaId = location.schemaId === rootSchemaId ? '' : location.schemaId
|
|
549
508
|
let functionCode = `
|
|
550
509
|
function ${functionName} (input) {
|
|
551
|
-
// ${
|
|
510
|
+
// ${schemaRef}
|
|
552
511
|
`
|
|
553
|
-
if (schema.nullable) {
|
|
554
|
-
functionCode += `
|
|
555
|
-
if (input === null) {
|
|
556
|
-
return 'null';
|
|
557
|
-
}
|
|
558
|
-
`
|
|
559
|
-
}
|
|
560
512
|
|
|
561
513
|
functionCode += `
|
|
562
514
|
var obj = ${toJSON('input')}
|
|
@@ -564,12 +516,7 @@ function buildObject (location) {
|
|
|
564
516
|
var addComma = false
|
|
565
517
|
`
|
|
566
518
|
|
|
567
|
-
|
|
568
|
-
functionCode += addIfThenElse(location)
|
|
569
|
-
} else {
|
|
570
|
-
functionCode += buildInnerObject(location)
|
|
571
|
-
}
|
|
572
|
-
|
|
519
|
+
functionCode += buildInnerObject(location)
|
|
573
520
|
functionCode += `
|
|
574
521
|
json += '}'
|
|
575
522
|
return json
|
|
@@ -581,48 +528,34 @@ function buildObject (location) {
|
|
|
581
528
|
}
|
|
582
529
|
|
|
583
530
|
function buildArray (location) {
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
// default to any items type
|
|
587
|
-
if (!schema.items) {
|
|
588
|
-
schema.items = {}
|
|
589
|
-
}
|
|
590
|
-
|
|
591
|
-
let itemsLocation = mergeLocation(location, 'items')
|
|
531
|
+
const schema = location.schema
|
|
592
532
|
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
location.schema = clone(location.schema)
|
|
596
|
-
schema = location.schema
|
|
597
|
-
schema[fjsCloned] = true
|
|
598
|
-
}
|
|
533
|
+
let itemsLocation = location.getPropertyLocation('items')
|
|
534
|
+
itemsLocation.schema = itemsLocation.schema || {}
|
|
599
535
|
|
|
600
|
-
|
|
601
|
-
itemsLocation =
|
|
602
|
-
schema.items = location.schema
|
|
536
|
+
if (itemsLocation.schema.$ref) {
|
|
537
|
+
itemsLocation = resolveRef(itemsLocation, itemsLocation.schema.$ref)
|
|
603
538
|
}
|
|
604
539
|
|
|
605
|
-
|
|
606
|
-
|
|
540
|
+
const itemsSchema = itemsLocation.schema
|
|
541
|
+
|
|
542
|
+
if (contextFunctionsNamesBySchema.has(schema)) {
|
|
543
|
+
return contextFunctionsNamesBySchema.get(schema)
|
|
607
544
|
}
|
|
608
545
|
|
|
609
546
|
const functionName = generateFuncName()
|
|
610
|
-
|
|
547
|
+
contextFunctionsNamesBySchema.set(schema, functionName)
|
|
548
|
+
|
|
549
|
+
let schemaRef = location.getSchemaRef()
|
|
550
|
+
if (schemaRef.startsWith(rootSchemaId)) {
|
|
551
|
+
schemaRef = schemaRef.replace(rootSchemaId, '')
|
|
552
|
+
}
|
|
611
553
|
|
|
612
|
-
const schemaId = location.schemaId === rootSchemaId ? '' : location.schemaId
|
|
613
554
|
let functionCode = `
|
|
614
555
|
function ${functionName} (obj) {
|
|
615
|
-
// ${
|
|
556
|
+
// ${schemaRef}
|
|
616
557
|
`
|
|
617
558
|
|
|
618
|
-
if (schema.nullable) {
|
|
619
|
-
functionCode += `
|
|
620
|
-
if (obj === null) {
|
|
621
|
-
return 'null';
|
|
622
|
-
}
|
|
623
|
-
`
|
|
624
|
-
}
|
|
625
|
-
|
|
626
559
|
functionCode += `
|
|
627
560
|
if (!Array.isArray(obj)) {
|
|
628
561
|
throw new TypeError(\`The value '$\{obj}' does not match schema definition.\`)
|
|
@@ -632,8 +565,8 @@ function buildArray (location) {
|
|
|
632
565
|
|
|
633
566
|
if (!schema.additionalItems) {
|
|
634
567
|
functionCode += `
|
|
635
|
-
if (arrayLength > ${
|
|
636
|
-
throw new Error(\`Item at ${
|
|
568
|
+
if (arrayLength > ${itemsSchema.length}) {
|
|
569
|
+
throw new Error(\`Item at ${itemsSchema.length} does not match schema definition.\`)
|
|
637
570
|
}
|
|
638
571
|
`
|
|
639
572
|
}
|
|
@@ -650,10 +583,10 @@ function buildArray (location) {
|
|
|
650
583
|
let jsonOutput = ''
|
|
651
584
|
`
|
|
652
585
|
|
|
653
|
-
if (Array.isArray(
|
|
654
|
-
for (let i = 0; i <
|
|
655
|
-
const item =
|
|
656
|
-
const tmpRes = buildValue(
|
|
586
|
+
if (Array.isArray(itemsSchema)) {
|
|
587
|
+
for (let i = 0; i < itemsSchema.length; i++) {
|
|
588
|
+
const item = itemsSchema[i]
|
|
589
|
+
const tmpRes = buildValue(itemsLocation.getPropertyLocation(i), `obj[${i}]`)
|
|
657
590
|
functionCode += `
|
|
658
591
|
if (${i} < arrayLength) {
|
|
659
592
|
if (${buildArrayTypeCondition(item.type, `[${i}]`)}) {
|
|
@@ -672,7 +605,7 @@ function buildArray (location) {
|
|
|
672
605
|
|
|
673
606
|
if (schema.additionalItems) {
|
|
674
607
|
functionCode += `
|
|
675
|
-
for (let i = ${
|
|
608
|
+
for (let i = ${itemsSchema.length}; i < arrayLength; i++) {
|
|
676
609
|
let json = JSON.stringify(obj[i])
|
|
677
610
|
jsonOutput += json
|
|
678
611
|
if (i < arrayLength - 1) {
|
|
@@ -743,6 +676,137 @@ function generateFuncName () {
|
|
|
743
676
|
return 'anonymous' + genFuncNameCounter++
|
|
744
677
|
}
|
|
745
678
|
|
|
679
|
+
function buildMultiTypeSerializer (location, input) {
|
|
680
|
+
const schema = location.schema
|
|
681
|
+
const types = schema.type.sort(t1 => t1 === 'null' ? -1 : 1)
|
|
682
|
+
|
|
683
|
+
let code = ''
|
|
684
|
+
|
|
685
|
+
types.forEach((type, index) => {
|
|
686
|
+
location.schema = { ...location.schema, type }
|
|
687
|
+
const nestedResult = buildSingleTypeSerializer(location, input)
|
|
688
|
+
|
|
689
|
+
const statement = index === 0 ? 'if' : 'else if'
|
|
690
|
+
switch (type) {
|
|
691
|
+
case 'null':
|
|
692
|
+
code += `
|
|
693
|
+
${statement} (${input} === null)
|
|
694
|
+
${nestedResult}
|
|
695
|
+
`
|
|
696
|
+
break
|
|
697
|
+
case 'string': {
|
|
698
|
+
code += `
|
|
699
|
+
${statement}(
|
|
700
|
+
typeof ${input} === "string" ||
|
|
701
|
+
${input} === null ||
|
|
702
|
+
${input} instanceof Date ||
|
|
703
|
+
${input} instanceof RegExp ||
|
|
704
|
+
(
|
|
705
|
+
typeof ${input} === "object" &&
|
|
706
|
+
typeof ${input}.toString === "function" &&
|
|
707
|
+
${input}.toString !== Object.prototype.toString &&
|
|
708
|
+
!(${input} instanceof Date)
|
|
709
|
+
)
|
|
710
|
+
)
|
|
711
|
+
${nestedResult}
|
|
712
|
+
`
|
|
713
|
+
break
|
|
714
|
+
}
|
|
715
|
+
case 'array': {
|
|
716
|
+
code += `
|
|
717
|
+
${statement}(Array.isArray(${input}))
|
|
718
|
+
${nestedResult}
|
|
719
|
+
`
|
|
720
|
+
break
|
|
721
|
+
}
|
|
722
|
+
case 'integer': {
|
|
723
|
+
code += `
|
|
724
|
+
${statement}(Number.isInteger(${input}) || ${input} === null)
|
|
725
|
+
${nestedResult}
|
|
726
|
+
`
|
|
727
|
+
break
|
|
728
|
+
}
|
|
729
|
+
default: {
|
|
730
|
+
code += `
|
|
731
|
+
${statement}(typeof ${input} === "${type}" || ${input} === null)
|
|
732
|
+
${nestedResult}
|
|
733
|
+
`
|
|
734
|
+
break
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
})
|
|
738
|
+
code += `
|
|
739
|
+
else throw new Error(\`The value $\{JSON.stringify(${input})} does not match schema definition.\`)
|
|
740
|
+
`
|
|
741
|
+
|
|
742
|
+
return code
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
function buildSingleTypeSerializer (location, input) {
|
|
746
|
+
const schema = location.schema
|
|
747
|
+
|
|
748
|
+
switch (schema.type) {
|
|
749
|
+
case 'null':
|
|
750
|
+
return 'json += \'null\''
|
|
751
|
+
case 'string': {
|
|
752
|
+
if (schema.format === 'date-time') {
|
|
753
|
+
return `json += serializer.asDateTime(${input})`
|
|
754
|
+
} else if (schema.format === 'date') {
|
|
755
|
+
return `json += serializer.asDate(${input})`
|
|
756
|
+
} else if (schema.format === 'time') {
|
|
757
|
+
return `json += serializer.asTime(${input})`
|
|
758
|
+
} else {
|
|
759
|
+
return `json += serializer.asString(${input})`
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
case 'integer':
|
|
763
|
+
return `json += serializer.asInteger(${input})`
|
|
764
|
+
case 'number':
|
|
765
|
+
return `json += serializer.asNumber(${input})`
|
|
766
|
+
case 'boolean':
|
|
767
|
+
return `json += serializer.asBoolean(${input})`
|
|
768
|
+
case 'object': {
|
|
769
|
+
const funcName = buildObject(location)
|
|
770
|
+
return `json += ${funcName}(${input})`
|
|
771
|
+
}
|
|
772
|
+
case 'array': {
|
|
773
|
+
const funcName = buildArray(location)
|
|
774
|
+
return `json += ${funcName}(${input})`
|
|
775
|
+
}
|
|
776
|
+
case undefined:
|
|
777
|
+
return `json += JSON.stringify(${input})`
|
|
778
|
+
default:
|
|
779
|
+
throw new Error(`${schema.type} unsupported`)
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
function buildConstSerializer (location, input) {
|
|
784
|
+
const schema = location.schema
|
|
785
|
+
const type = schema.type
|
|
786
|
+
|
|
787
|
+
const hasNullType = Array.isArray(type) && type.includes('null')
|
|
788
|
+
|
|
789
|
+
let code = ''
|
|
790
|
+
|
|
791
|
+
if (hasNullType) {
|
|
792
|
+
code += `
|
|
793
|
+
if (${input} === null) {
|
|
794
|
+
json += 'null'
|
|
795
|
+
} else {
|
|
796
|
+
`
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
code += `json += '${JSON.stringify(schema.const)}'`
|
|
800
|
+
|
|
801
|
+
if (hasNullType) {
|
|
802
|
+
code += `
|
|
803
|
+
}
|
|
804
|
+
`
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
return code
|
|
808
|
+
}
|
|
809
|
+
|
|
746
810
|
function buildValue (location, input) {
|
|
747
811
|
let schema = location.schema
|
|
748
812
|
|
|
@@ -762,196 +826,73 @@ function buildValue (location, input) {
|
|
|
762
826
|
}
|
|
763
827
|
}
|
|
764
828
|
|
|
829
|
+
if (schema.if && schema.then) {
|
|
830
|
+
return addIfThenElse(location, input)
|
|
831
|
+
}
|
|
832
|
+
|
|
765
833
|
if (schema.allOf) {
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
schema = mergedSchema
|
|
769
|
-
location.schema = mergedSchema
|
|
834
|
+
mergeAllOfSchema(location, schema, clone(schema))
|
|
835
|
+
schema = location.schema
|
|
770
836
|
}
|
|
771
837
|
|
|
772
838
|
const type = schema.type
|
|
773
|
-
const nullable = schema.nullable === true || (Array.isArray(type) && type.includes('null'))
|
|
774
839
|
|
|
775
840
|
let code = ''
|
|
776
|
-
let funcName
|
|
777
841
|
|
|
778
|
-
if (
|
|
779
|
-
|
|
842
|
+
if (type === undefined && (schema.anyOf || schema.oneOf)) {
|
|
843
|
+
location.isValidated = true
|
|
844
|
+
validatorSchemasIds.add(location.getSchemaId())
|
|
845
|
+
|
|
846
|
+
const type = schema.anyOf ? 'anyOf' : 'oneOf'
|
|
847
|
+
const anyOfLocation = location.getPropertyLocation(type)
|
|
848
|
+
|
|
849
|
+
for (let index = 0; index < location.schema[type].length; index++) {
|
|
850
|
+
const optionLocation = anyOfLocation.getPropertyLocation(index)
|
|
851
|
+
const schemaRef = optionLocation.getSchemaRef()
|
|
852
|
+
const nestedResult = buildValue(optionLocation, input)
|
|
780
853
|
code += `
|
|
781
|
-
|
|
854
|
+
${index === 0 ? 'if' : 'else if'}(validator.validate("${schemaRef}", ${input}))
|
|
855
|
+
${nestedResult}
|
|
782
856
|
`
|
|
783
|
-
return code
|
|
784
857
|
}
|
|
785
|
-
|
|
858
|
+
|
|
859
|
+
code += `
|
|
860
|
+
else throw new Error(\`The value $\{JSON.stringify(${input})} does not match schema definition.\`)
|
|
861
|
+
`
|
|
786
862
|
return code
|
|
787
863
|
}
|
|
788
864
|
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
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
|
-
}
|
|
803
|
-
code += `json += ${funcName}(${input})`
|
|
804
|
-
break
|
|
805
|
-
}
|
|
806
|
-
case 'integer':
|
|
807
|
-
funcName = nullable ? 'serializer.asIntegerNullable.bind(serializer)' : 'serializer.asInteger.bind(serializer)'
|
|
808
|
-
code += `json += ${funcName}(${input})`
|
|
809
|
-
break
|
|
810
|
-
case 'number':
|
|
811
|
-
funcName = nullable ? 'serializer.asNumberNullable.bind(serializer)' : 'serializer.asNumber.bind(serializer)'
|
|
812
|
-
code += `json += ${funcName}(${input})`
|
|
813
|
-
break
|
|
814
|
-
case 'boolean':
|
|
815
|
-
funcName = nullable ? 'serializer.asBooleanNullable.bind(serializer)' : 'serializer.asBoolean.bind(serializer)'
|
|
816
|
-
code += `json += ${funcName}(${input})`
|
|
817
|
-
break
|
|
818
|
-
case 'object':
|
|
819
|
-
funcName = buildObject(location)
|
|
820
|
-
code += `json += ${funcName}(${input})`
|
|
821
|
-
break
|
|
822
|
-
case 'array':
|
|
823
|
-
funcName = buildArray(location)
|
|
824
|
-
code += `json += ${funcName}(${input})`
|
|
825
|
-
break
|
|
826
|
-
case undefined:
|
|
827
|
-
if (schema.anyOf || schema.oneOf) {
|
|
828
|
-
// beware: dereferenceOfRefs has side effects and changes schema.anyOf
|
|
829
|
-
const type = schema.anyOf ? 'anyOf' : 'oneOf'
|
|
830
|
-
const anyOfLocation = mergeLocation(location, type)
|
|
831
|
-
|
|
832
|
-
for (let index = 0; index < location.schema[type].length; index++) {
|
|
833
|
-
const optionLocation = mergeLocation(anyOfLocation, index)
|
|
834
|
-
const schemaRef = optionLocation.schemaId + optionLocation.jsonPointer
|
|
835
|
-
const nestedResult = buildValue(optionLocation, input)
|
|
836
|
-
code += `
|
|
837
|
-
${index === 0 ? 'if' : 'else if'}(validator.validate("${schemaRef}", ${input}))
|
|
838
|
-
${nestedResult}
|
|
839
|
-
`
|
|
840
|
-
}
|
|
841
|
-
|
|
842
|
-
code += `
|
|
843
|
-
else throw new Error(\`The value $\{JSON.stringify(${input})} does not match schema definition.\`)
|
|
844
|
-
`
|
|
845
|
-
} else if (isEmpty(schema)) {
|
|
846
|
-
code += `
|
|
847
|
-
json += JSON.stringify(${input})
|
|
848
|
-
`
|
|
865
|
+
const nullable = schema.nullable === true
|
|
866
|
+
if (nullable) {
|
|
867
|
+
code += `
|
|
868
|
+
if (${input} === null) {
|
|
869
|
+
json += 'null'
|
|
849
870
|
} else {
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
`
|
|
853
|
-
}
|
|
854
|
-
break
|
|
855
|
-
default:
|
|
856
|
-
if (Array.isArray(type)) {
|
|
857
|
-
let sortedTypes = type
|
|
858
|
-
const nullable = schema.nullable === true || type.includes('null')
|
|
859
|
-
|
|
860
|
-
if (nullable) {
|
|
861
|
-
sortedTypes = sortedTypes.filter(type => type !== 'null')
|
|
862
|
-
code += `
|
|
863
|
-
if (${input} === null) {
|
|
864
|
-
json += null
|
|
865
|
-
} else {`
|
|
866
|
-
}
|
|
871
|
+
`
|
|
872
|
+
}
|
|
867
873
|
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
code += `
|
|
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
|
-
)
|
|
888
|
-
${nestedResult}
|
|
889
|
-
`
|
|
890
|
-
break
|
|
891
|
-
}
|
|
892
|
-
case 'array': {
|
|
893
|
-
code += `
|
|
894
|
-
${statement}(Array.isArray(${input}))
|
|
895
|
-
${nestedResult}
|
|
896
|
-
`
|
|
897
|
-
break
|
|
898
|
-
}
|
|
899
|
-
case 'integer': {
|
|
900
|
-
code += `
|
|
901
|
-
${statement}(Number.isInteger(${input}) || ${input} === null)
|
|
902
|
-
${nestedResult}
|
|
903
|
-
`
|
|
904
|
-
break
|
|
905
|
-
}
|
|
906
|
-
case 'object': {
|
|
907
|
-
code += `
|
|
908
|
-
${statement}(typeof ${input} === "object" || ${input} === null)
|
|
909
|
-
${nestedResult}
|
|
910
|
-
`
|
|
911
|
-
break
|
|
912
|
-
}
|
|
913
|
-
default: {
|
|
914
|
-
code += `
|
|
915
|
-
${statement}(typeof ${input} === "${type}" || ${input} === null)
|
|
916
|
-
${nestedResult}
|
|
917
|
-
`
|
|
918
|
-
break
|
|
919
|
-
}
|
|
920
|
-
}
|
|
921
|
-
})
|
|
922
|
-
code += `
|
|
923
|
-
else throw new Error(\`The value $\{JSON.stringify(${input})} does not match schema definition.\`)
|
|
924
|
-
`
|
|
874
|
+
if (schema.const !== undefined) {
|
|
875
|
+
code += buildConstSerializer(location, input)
|
|
876
|
+
} else if (Array.isArray(type)) {
|
|
877
|
+
code += buildMultiTypeSerializer(location, input)
|
|
878
|
+
} else {
|
|
879
|
+
code += buildSingleTypeSerializer(location, input)
|
|
880
|
+
}
|
|
925
881
|
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
}
|
|
929
|
-
`
|
|
930
|
-
}
|
|
931
|
-
} else {
|
|
932
|
-
throw new Error(`${type} unsupported`)
|
|
882
|
+
if (nullable) {
|
|
883
|
+
code += `
|
|
933
884
|
}
|
|
885
|
+
`
|
|
934
886
|
}
|
|
935
887
|
|
|
936
888
|
return code
|
|
937
889
|
}
|
|
938
890
|
|
|
939
|
-
function isEmpty (schema) {
|
|
940
|
-
// eslint-disable-next-line
|
|
941
|
-
for (var key in schema) {
|
|
942
|
-
if (Object.prototype.hasOwnProperty.call(schema, key) && schema[key] !== undefined) {
|
|
943
|
-
return false
|
|
944
|
-
}
|
|
945
|
-
}
|
|
946
|
-
return true
|
|
947
|
-
}
|
|
948
|
-
|
|
949
891
|
module.exports = build
|
|
950
892
|
|
|
951
893
|
module.exports.validLargeArrayMechanisms = validLargeArrayMechanisms
|
|
952
894
|
|
|
953
|
-
module.exports.restore = function ({ code, validator }) {
|
|
954
|
-
const serializer = new Serializer()
|
|
895
|
+
module.exports.restore = function ({ code, validator, serializer }) {
|
|
955
896
|
// eslint-disable-next-line
|
|
956
897
|
return (Function.apply(null, ['validator', 'serializer', code])
|
|
957
898
|
.apply(null, [validator, serializer]))
|