fast-json-stringify 4.2.0 → 5.0.2
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 +0 -2
- package/ajv.js +1 -1
- package/benchmark/bench-cmp-branch.js +16 -14
- package/index.js +152 -280
- package/package.json +4 -9
- package/serializer.js +18 -23
- package/test/Infinity.test.js +39 -0
- package/test/allof.test.js +9 -7
- package/test/if-then-else.test.js +35 -0
- package/test/ref.test.js +679 -37
package/index.js
CHANGED
|
@@ -12,7 +12,6 @@ const Serializer = require('./serializer')
|
|
|
12
12
|
const buildAjv = require('./ajv')
|
|
13
13
|
|
|
14
14
|
let largeArraySize = 2e4
|
|
15
|
-
let stringSimilarity = null
|
|
16
15
|
let largeArrayMechanism = 'default'
|
|
17
16
|
const validLargeArrayMechanisms = [
|
|
18
17
|
'default',
|
|
@@ -41,36 +40,83 @@ function isValidSchema (schema, name) {
|
|
|
41
40
|
}
|
|
42
41
|
}
|
|
43
42
|
|
|
44
|
-
function mergeLocation (
|
|
43
|
+
function mergeLocation (location, key) {
|
|
45
44
|
return {
|
|
46
|
-
schema:
|
|
47
|
-
|
|
48
|
-
|
|
45
|
+
schema: location.schema[key],
|
|
46
|
+
schemaId: location.schemaId,
|
|
47
|
+
jsonPointer: location.jsonPointer + '/' + key
|
|
49
48
|
}
|
|
50
49
|
}
|
|
51
50
|
|
|
51
|
+
function resolveRef (location, ref) {
|
|
52
|
+
let hashIndex = ref.indexOf('#')
|
|
53
|
+
if (hashIndex === -1) {
|
|
54
|
+
hashIndex = ref.length
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const schemaId = ref.slice(0, hashIndex) || location.schemaId
|
|
58
|
+
const jsonPointer = ref.slice(hashIndex)
|
|
59
|
+
|
|
60
|
+
const schemaRef = schemaId + jsonPointer
|
|
61
|
+
|
|
62
|
+
let ajvSchema
|
|
63
|
+
try {
|
|
64
|
+
ajvSchema = ajvInstance.getSchema(schemaRef)
|
|
65
|
+
} catch (error) {
|
|
66
|
+
throw new Error(`Cannot find reference "${ref}"`)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (ajvSchema === undefined) {
|
|
70
|
+
throw new Error(`Cannot find reference "${ref}"`)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const schema = ajvSchema.schema
|
|
74
|
+
if (schema.$ref !== undefined) {
|
|
75
|
+
return resolveRef({ schema, schemaId, jsonPointer }, schema.$ref)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return { schema, schemaId, jsonPointer }
|
|
79
|
+
}
|
|
80
|
+
|
|
52
81
|
const arrayItemsReferenceSerializersMap = new Map()
|
|
53
82
|
const objectReferenceSerializersMap = new Map()
|
|
54
|
-
const schemaReferenceMap = new Map()
|
|
55
83
|
|
|
84
|
+
let rootSchemaId = null
|
|
56
85
|
let ajvInstance = null
|
|
57
86
|
let contextFunctions = null
|
|
58
87
|
|
|
59
88
|
function build (schema, options) {
|
|
89
|
+
schema = clone(schema)
|
|
90
|
+
|
|
60
91
|
arrayItemsReferenceSerializersMap.clear()
|
|
61
92
|
objectReferenceSerializersMap.clear()
|
|
62
|
-
schemaReferenceMap.clear()
|
|
63
93
|
|
|
64
94
|
contextFunctions = []
|
|
65
95
|
options = options || {}
|
|
66
96
|
|
|
67
97
|
ajvInstance = buildAjv(options.ajv)
|
|
98
|
+
rootSchemaId = schema.$id || randomUUID()
|
|
68
99
|
|
|
69
100
|
isValidSchema(schema)
|
|
101
|
+
extendDateTimeType(schema)
|
|
102
|
+
ajvInstance.addSchema(schema, rootSchemaId)
|
|
103
|
+
|
|
70
104
|
if (options.schema) {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
105
|
+
const externalSchemas = clone(options.schema)
|
|
106
|
+
|
|
107
|
+
for (const key of Object.keys(externalSchemas)) {
|
|
108
|
+
const externalSchema = externalSchemas[key]
|
|
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
|
+
}
|
|
74
120
|
}
|
|
75
121
|
}
|
|
76
122
|
|
|
@@ -98,22 +144,8 @@ function build (schema, options) {
|
|
|
98
144
|
|
|
99
145
|
const serializer = new Serializer(options)
|
|
100
146
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
root: schema,
|
|
104
|
-
externalSchema: options.schema
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
if (schema.$ref) {
|
|
108
|
-
location = refFinder(schema.$ref, location)
|
|
109
|
-
schema = location.schema
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
if (schema.type === undefined) {
|
|
113
|
-
schema.type = inferTypeByKeyword(schema)
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
const code = buildValue('main', 'input', location)
|
|
147
|
+
const location = { schema, schemaId: rootSchemaId, jsonPointer: '#' }
|
|
148
|
+
const code = buildValue(location, 'input')
|
|
117
149
|
|
|
118
150
|
const contextFunctionCode = `
|
|
119
151
|
function main (input) {
|
|
@@ -146,10 +178,10 @@ function build (schema, options) {
|
|
|
146
178
|
const stringifyFunc = contextFunc(ajvInstance, serializer)
|
|
147
179
|
|
|
148
180
|
ajvInstance = null
|
|
181
|
+
rootSchemaId = null
|
|
149
182
|
contextFunctions = null
|
|
150
183
|
arrayItemsReferenceSerializersMap.clear()
|
|
151
184
|
objectReferenceSerializersMap.clear()
|
|
152
|
-
schemaReferenceMap.clear()
|
|
153
185
|
|
|
154
186
|
return stringifyFunc
|
|
155
187
|
}
|
|
@@ -230,10 +262,11 @@ function addPatternProperties (location) {
|
|
|
230
262
|
if (properties[keys[i]]) continue
|
|
231
263
|
`
|
|
232
264
|
|
|
233
|
-
|
|
234
|
-
|
|
265
|
+
const patternPropertiesLocation = mergeLocation(location, 'patternProperties')
|
|
266
|
+
Object.keys(pp).forEach((regex) => {
|
|
267
|
+
let ppLocation = mergeLocation(patternPropertiesLocation, regex)
|
|
235
268
|
if (pp[regex].$ref) {
|
|
236
|
-
ppLocation =
|
|
269
|
+
ppLocation = resolveRef(ppLocation, pp[regex].$ref)
|
|
237
270
|
pp[regex] = ppLocation.schema
|
|
238
271
|
}
|
|
239
272
|
|
|
@@ -243,7 +276,7 @@ function addPatternProperties (location) {
|
|
|
243
276
|
throw new Error(`${err.message}. Found at ${regex} matching ${JSON.stringify(pp[regex])}`)
|
|
244
277
|
}
|
|
245
278
|
|
|
246
|
-
const valueCode = buildValue(
|
|
279
|
+
const valueCode = buildValue(ppLocation, 'obj[keys[i]]')
|
|
247
280
|
code += `
|
|
248
281
|
if (/${regex.replace(/\\*\//g, '\\/')}/.test(keys[i])) {
|
|
249
282
|
${addComma}
|
|
@@ -264,7 +297,7 @@ function addPatternProperties (location) {
|
|
|
264
297
|
}
|
|
265
298
|
|
|
266
299
|
function additionalProperty (location) {
|
|
267
|
-
|
|
300
|
+
const ap = location.schema.additionalProperties
|
|
268
301
|
let code = ''
|
|
269
302
|
if (ap === true) {
|
|
270
303
|
code += `
|
|
@@ -276,13 +309,13 @@ function additionalProperty (location) {
|
|
|
276
309
|
|
|
277
310
|
return code
|
|
278
311
|
}
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
312
|
+
|
|
313
|
+
let apLocation = mergeLocation(location, 'additionalProperties')
|
|
314
|
+
if (apLocation.schema.$ref) {
|
|
315
|
+
apLocation = resolveRef(apLocation, apLocation.schema.$ref)
|
|
283
316
|
}
|
|
284
317
|
|
|
285
|
-
const valueCode = buildValue(
|
|
318
|
+
const valueCode = buildValue(apLocation, 'obj[keys[i]]')
|
|
286
319
|
|
|
287
320
|
code += `
|
|
288
321
|
${addComma}
|
|
@@ -304,140 +337,9 @@ function addAdditionalProperties (location) {
|
|
|
304
337
|
`
|
|
305
338
|
}
|
|
306
339
|
|
|
307
|
-
function
|
|
308
|
-
let objSchema
|
|
309
|
-
const explore = (schema, searchedId) => {
|
|
310
|
-
Object.keys(schema || {}).forEach((key, i, a) => {
|
|
311
|
-
if (key === '$id' && schema[key] === searchedId) {
|
|
312
|
-
objSchema = schema
|
|
313
|
-
} else if (objSchema === undefined && typeof schema[key] === 'object') {
|
|
314
|
-
explore(schema[key], searchedId)
|
|
315
|
-
}
|
|
316
|
-
})
|
|
317
|
-
}
|
|
318
|
-
explore(schema, searchedId)
|
|
319
|
-
return objSchema
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
function refFinder (ref, location) {
|
|
323
|
-
const externalSchema = location.externalSchema
|
|
324
|
-
let root = location.root
|
|
325
|
-
let schema = location.schema
|
|
326
|
-
|
|
327
|
-
if (externalSchema && externalSchema[ref]) {
|
|
328
|
-
return {
|
|
329
|
-
schema: externalSchema[ref],
|
|
330
|
-
root: externalSchema[ref],
|
|
331
|
-
externalSchema
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
// Split file from walk
|
|
336
|
-
ref = ref.split('#')
|
|
337
|
-
|
|
338
|
-
// Check schemaReferenceMap for $id entry
|
|
339
|
-
if (ref[0] && schemaReferenceMap.has(ref[0])) {
|
|
340
|
-
schema = schemaReferenceMap.get(ref[0])
|
|
341
|
-
root = schemaReferenceMap.get(ref[0])
|
|
342
|
-
if (schema.$ref) {
|
|
343
|
-
return refFinder(schema.$ref, {
|
|
344
|
-
schema,
|
|
345
|
-
root,
|
|
346
|
-
externalSchema
|
|
347
|
-
})
|
|
348
|
-
}
|
|
349
|
-
} else if (ref[0]) { // If external file
|
|
350
|
-
schema = externalSchema[ref[0]]
|
|
351
|
-
root = externalSchema[ref[0]]
|
|
352
|
-
|
|
353
|
-
if (schema === undefined) {
|
|
354
|
-
findBadKey(externalSchema, [ref[0]])
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
if (schema.$ref) {
|
|
358
|
-
return refFinder(schema.$ref, {
|
|
359
|
-
schema,
|
|
360
|
-
root,
|
|
361
|
-
externalSchema
|
|
362
|
-
})
|
|
363
|
-
}
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
let code = 'return schema'
|
|
367
|
-
// If it has a path
|
|
368
|
-
if (ref[1]) {
|
|
369
|
-
// ref[1] could contain a JSON pointer - ex: /definitions/num
|
|
370
|
-
// or plain name fragment id without suffix # - ex: customId
|
|
371
|
-
const walk = ref[1].split('/')
|
|
372
|
-
if (walk.length === 1) {
|
|
373
|
-
const targetId = `#${ref[1]}`
|
|
374
|
-
let dereferenced = idFinder(schema, targetId)
|
|
375
|
-
if (dereferenced === undefined && !ref[0]) {
|
|
376
|
-
// eslint-disable-next-line
|
|
377
|
-
for (var key of Object.keys(externalSchema)) {
|
|
378
|
-
dereferenced = idFinder(externalSchema[key], targetId)
|
|
379
|
-
if (dereferenced !== undefined) {
|
|
380
|
-
root = externalSchema[key]
|
|
381
|
-
break
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
return {
|
|
387
|
-
schema: dereferenced,
|
|
388
|
-
root,
|
|
389
|
-
externalSchema
|
|
390
|
-
}
|
|
391
|
-
} else {
|
|
392
|
-
// eslint-disable-next-line
|
|
393
|
-
for (var i = 1; i < walk.length; i++) {
|
|
394
|
-
code += `[${JSON.stringify(walk[i])}]`
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
}
|
|
398
|
-
let result
|
|
399
|
-
try {
|
|
400
|
-
result = (new Function('schema', code))(root)
|
|
401
|
-
} catch (err) {}
|
|
402
|
-
|
|
403
|
-
if (result === undefined && ref[1]) {
|
|
404
|
-
const walk = ref[1].split('/')
|
|
405
|
-
findBadKey(schema, walk.slice(1))
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
if (result.$ref) {
|
|
409
|
-
return refFinder(result.$ref, {
|
|
410
|
-
schema,
|
|
411
|
-
root,
|
|
412
|
-
externalSchema
|
|
413
|
-
})
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
return {
|
|
417
|
-
schema: result,
|
|
418
|
-
root,
|
|
419
|
-
externalSchema
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
function findBadKey (obj, keys) {
|
|
423
|
-
if (keys.length === 0) return null
|
|
424
|
-
const key = keys.shift()
|
|
425
|
-
if (obj[key] === undefined) {
|
|
426
|
-
stringSimilarity = stringSimilarity || require('string-similarity')
|
|
427
|
-
const { bestMatch } = stringSimilarity.findBestMatch(key, Object.keys(obj))
|
|
428
|
-
if (bestMatch.rating >= 0.5) {
|
|
429
|
-
throw new Error(`Cannot find reference ${JSON.stringify(key)}, did you mean ${JSON.stringify(bestMatch.target)}?`)
|
|
430
|
-
} else {
|
|
431
|
-
throw new Error(`Cannot find reference ${JSON.stringify(key)}`)
|
|
432
|
-
}
|
|
433
|
-
}
|
|
434
|
-
return findBadKey(obj[key], keys)
|
|
435
|
-
}
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
function buildCode (location, locationPath) {
|
|
340
|
+
function buildCode (location) {
|
|
439
341
|
if (location.schema.$ref) {
|
|
440
|
-
location =
|
|
342
|
+
location = resolveRef(location, location.schema.$ref)
|
|
441
343
|
}
|
|
442
344
|
|
|
443
345
|
const schema = location.schema
|
|
@@ -445,10 +347,11 @@ function buildCode (location, locationPath) {
|
|
|
445
347
|
|
|
446
348
|
let code = ''
|
|
447
349
|
|
|
350
|
+
const propertiesLocation = mergeLocation(location, 'properties')
|
|
448
351
|
Object.keys(schema.properties || {}).forEach((key) => {
|
|
449
|
-
let propertyLocation = mergeLocation(
|
|
352
|
+
let propertyLocation = mergeLocation(propertiesLocation, key)
|
|
450
353
|
if (schema.properties[key].$ref) {
|
|
451
|
-
propertyLocation =
|
|
354
|
+
propertyLocation = resolveRef(location, schema.properties[key].$ref)
|
|
452
355
|
schema.properties[key] = propertyLocation.schema
|
|
453
356
|
}
|
|
454
357
|
|
|
@@ -464,7 +367,7 @@ function buildCode (location, locationPath) {
|
|
|
464
367
|
json += ${asString} + ':'
|
|
465
368
|
`
|
|
466
369
|
|
|
467
|
-
code += buildValue(
|
|
370
|
+
code += buildValue(propertyLocation, `obj[${JSON.stringify(key)}]`)
|
|
468
371
|
|
|
469
372
|
const defaultValue = schema.properties[key].default
|
|
470
373
|
if (defaultValue !== undefined) {
|
|
@@ -494,9 +397,14 @@ function buildCode (location, locationPath) {
|
|
|
494
397
|
}
|
|
495
398
|
|
|
496
399
|
function mergeAllOfSchema (location, schema, mergedSchema) {
|
|
497
|
-
|
|
400
|
+
const allOfLocation = mergeLocation(location, 'allOf')
|
|
401
|
+
|
|
402
|
+
for (let i = 0; i < schema.allOf.length; i++) {
|
|
403
|
+
let allOfSchema = schema.allOf[i]
|
|
404
|
+
|
|
498
405
|
if (allOfSchema.$ref) {
|
|
499
|
-
|
|
406
|
+
const allOfSchemaLocation = mergeLocation(allOfLocation, i)
|
|
407
|
+
allOfSchema = resolveRef(allOfSchemaLocation, allOfSchema.$ref).schema
|
|
500
408
|
}
|
|
501
409
|
|
|
502
410
|
let allOfSchemaType = allOfSchema.type
|
|
@@ -583,9 +491,9 @@ function mergeAllOfSchema (location, schema, mergedSchema) {
|
|
|
583
491
|
delete mergedSchema.allOf
|
|
584
492
|
}
|
|
585
493
|
|
|
586
|
-
function buildInnerObject (location
|
|
494
|
+
function buildInnerObject (location) {
|
|
587
495
|
const schema = location.schema
|
|
588
|
-
let code = buildCode(location
|
|
496
|
+
let code = buildCode(location)
|
|
589
497
|
if (schema.patternProperties) {
|
|
590
498
|
code += addPatternProperties(location)
|
|
591
499
|
} else if (schema.additionalProperties && !schema.patternProperties) {
|
|
@@ -594,49 +502,46 @@ function buildInnerObject (location, locationPath) {
|
|
|
594
502
|
return code
|
|
595
503
|
}
|
|
596
504
|
|
|
597
|
-
function addIfThenElse (location
|
|
598
|
-
|
|
505
|
+
function addIfThenElse (location) {
|
|
506
|
+
const schema = merge({}, location.schema)
|
|
507
|
+
const thenSchema = schema.then
|
|
508
|
+
const elseSchema = schema.else || { additionalProperties: true }
|
|
599
509
|
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
const then = copy.then
|
|
604
|
-
const e = copy.else ? copy.else : { additionalProperties: true }
|
|
605
|
-
delete copy.if
|
|
606
|
-
delete copy.then
|
|
607
|
-
delete copy.else
|
|
608
|
-
let merged = merge(copy, then)
|
|
609
|
-
let mergedLocation = mergeLocation(location, { schema: merged })
|
|
610
|
-
|
|
611
|
-
const schemaKey = i.$id || randomUUID()
|
|
612
|
-
ajvInstance.addSchema(i, schemaKey)
|
|
510
|
+
delete schema.if
|
|
511
|
+
delete schema.then
|
|
512
|
+
delete schema.else
|
|
613
513
|
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
514
|
+
const ifLocation = mergeLocation(location, 'if')
|
|
515
|
+
const ifSchemaRef = ifLocation.schemaId + ifLocation.jsonPointer
|
|
516
|
+
|
|
517
|
+
let code = `
|
|
518
|
+
if (ajv.validate("${ifSchemaRef}", obj)) {
|
|
617
519
|
`
|
|
618
|
-
if (merged.if && merged.then) {
|
|
619
|
-
code += addIfThenElse(mergedLocation, locationPath + 'Then')
|
|
620
|
-
}
|
|
621
520
|
|
|
622
|
-
|
|
521
|
+
const thenLocation = mergeLocation(location, 'then')
|
|
522
|
+
thenLocation.schema = merge(schema, thenSchema)
|
|
623
523
|
|
|
524
|
+
if (thenSchema.if && thenSchema.then) {
|
|
525
|
+
code += addIfThenElse(thenLocation)
|
|
526
|
+
} else {
|
|
527
|
+
code += buildInnerObject(thenLocation)
|
|
528
|
+
}
|
|
624
529
|
code += `
|
|
625
530
|
}
|
|
626
531
|
`
|
|
627
|
-
|
|
628
|
-
|
|
532
|
+
|
|
533
|
+
const elseLocation = mergeLocation(location, 'else')
|
|
534
|
+
elseLocation.schema = merge(schema, elseSchema)
|
|
629
535
|
|
|
630
536
|
code += `
|
|
631
537
|
else {
|
|
632
538
|
`
|
|
633
539
|
|
|
634
|
-
if (
|
|
635
|
-
code += addIfThenElse(
|
|
540
|
+
if (elseSchema.if && elseSchema.then) {
|
|
541
|
+
code += addIfThenElse(elseLocation)
|
|
542
|
+
} else {
|
|
543
|
+
code += buildInnerObject(elseLocation)
|
|
636
544
|
}
|
|
637
|
-
|
|
638
|
-
code += buildInnerObject(mergedLocation, locationPath + 'Else')
|
|
639
|
-
|
|
640
545
|
code += `
|
|
641
546
|
}
|
|
642
547
|
`
|
|
@@ -650,11 +555,8 @@ function toJSON (variableName) {
|
|
|
650
555
|
`
|
|
651
556
|
}
|
|
652
557
|
|
|
653
|
-
function buildObject (location
|
|
558
|
+
function buildObject (location) {
|
|
654
559
|
const schema = location.schema
|
|
655
|
-
if (schema.$id !== undefined) {
|
|
656
|
-
schemaReferenceMap.set(schema.$id, schema)
|
|
657
|
-
}
|
|
658
560
|
|
|
659
561
|
if (objectReferenceSerializersMap.has(schema)) {
|
|
660
562
|
return objectReferenceSerializersMap.get(schema)
|
|
@@ -663,9 +565,10 @@ function buildObject (location, locationPath) {
|
|
|
663
565
|
const functionName = generateFuncName()
|
|
664
566
|
objectReferenceSerializersMap.set(schema, functionName)
|
|
665
567
|
|
|
568
|
+
const schemaId = location.schemaId === rootSchemaId ? '' : location.schemaId
|
|
666
569
|
let functionCode = `
|
|
667
570
|
function ${functionName} (input) {
|
|
668
|
-
// ${
|
|
571
|
+
// ${schemaId + location.jsonPointer}
|
|
669
572
|
`
|
|
670
573
|
if (schema.nullable) {
|
|
671
574
|
functionCode += `
|
|
@@ -681,18 +584,13 @@ function buildObject (location, locationPath) {
|
|
|
681
584
|
var addComma = false
|
|
682
585
|
`
|
|
683
586
|
|
|
684
|
-
let rCode
|
|
685
587
|
if (schema.if && schema.then) {
|
|
686
|
-
functionCode +=
|
|
687
|
-
var valid
|
|
688
|
-
`
|
|
689
|
-
rCode = addIfThenElse(location, locationPath)
|
|
588
|
+
functionCode += addIfThenElse(location)
|
|
690
589
|
} else {
|
|
691
|
-
|
|
590
|
+
functionCode += buildInnerObject(location)
|
|
692
591
|
}
|
|
693
592
|
|
|
694
|
-
|
|
695
|
-
functionCode += `${rCode}
|
|
593
|
+
functionCode += `
|
|
696
594
|
json += '}'
|
|
697
595
|
return json
|
|
698
596
|
}
|
|
@@ -702,17 +600,16 @@ function buildObject (location, locationPath) {
|
|
|
702
600
|
return functionName
|
|
703
601
|
}
|
|
704
602
|
|
|
705
|
-
function buildArray (location
|
|
603
|
+
function buildArray (location) {
|
|
706
604
|
let schema = location.schema
|
|
707
|
-
if (schema.$id !== undefined) {
|
|
708
|
-
schemaReferenceMap.set(schema.$id, schema)
|
|
709
|
-
}
|
|
710
605
|
|
|
711
606
|
// default to any items type
|
|
712
607
|
if (!schema.items) {
|
|
713
608
|
schema.items = {}
|
|
714
609
|
}
|
|
715
610
|
|
|
611
|
+
let itemsLocation = mergeLocation(location, 'items')
|
|
612
|
+
|
|
716
613
|
if (schema.items.$ref) {
|
|
717
614
|
if (!schema[fjsCloned]) {
|
|
718
615
|
location.schema = clone(location.schema)
|
|
@@ -720,7 +617,8 @@ function buildArray (location, locationPath) {
|
|
|
720
617
|
schema[fjsCloned] = true
|
|
721
618
|
}
|
|
722
619
|
|
|
723
|
-
location =
|
|
620
|
+
location = resolveRef(location, schema.items.$ref)
|
|
621
|
+
itemsLocation = location
|
|
724
622
|
schema.items = location.schema
|
|
725
623
|
}
|
|
726
624
|
|
|
@@ -731,9 +629,10 @@ function buildArray (location, locationPath) {
|
|
|
731
629
|
const functionName = generateFuncName()
|
|
732
630
|
arrayItemsReferenceSerializersMap.set(schema.items, functionName)
|
|
733
631
|
|
|
632
|
+
const schemaId = location.schemaId === rootSchemaId ? '' : location.schemaId
|
|
734
633
|
let functionCode = `
|
|
735
634
|
function ${functionName} (obj) {
|
|
736
|
-
// ${
|
|
635
|
+
// ${schemaId + location.jsonPointer}
|
|
737
636
|
`
|
|
738
637
|
|
|
739
638
|
if (schema.nullable) {
|
|
@@ -771,11 +670,10 @@ function buildArray (location, locationPath) {
|
|
|
771
670
|
let jsonOutput = ''
|
|
772
671
|
`
|
|
773
672
|
|
|
774
|
-
const accessor = '[i]'
|
|
775
673
|
if (Array.isArray(schema.items)) {
|
|
776
674
|
for (let i = 0; i < schema.items.length; i++) {
|
|
777
675
|
const item = schema.items[i]
|
|
778
|
-
const tmpRes = buildValue(
|
|
676
|
+
const tmpRes = buildValue(mergeLocation(itemsLocation, i), `obj[${i}]`)
|
|
779
677
|
functionCode += `
|
|
780
678
|
if (${i} < arrayLength) {
|
|
781
679
|
if (${buildArrayTypeCondition(item.type, `[${i}]`)}) {
|
|
@@ -803,7 +701,7 @@ function buildArray (location, locationPath) {
|
|
|
803
701
|
}`
|
|
804
702
|
}
|
|
805
703
|
} else {
|
|
806
|
-
const code = buildValue(
|
|
704
|
+
const code = buildValue(itemsLocation, 'obj[i]')
|
|
807
705
|
functionCode += `
|
|
808
706
|
for (let i = 0; i < arrayLength; i++) {
|
|
809
707
|
let json = ''
|
|
@@ -860,40 +758,17 @@ function buildArrayTypeCondition (type, accessor) {
|
|
|
860
758
|
return condition
|
|
861
759
|
}
|
|
862
760
|
|
|
863
|
-
function dereferenceOfRefs (location, type) {
|
|
864
|
-
if (!location.schema[fjsCloned]) {
|
|
865
|
-
const schemaClone = clone(location.schema)
|
|
866
|
-
schemaClone[fjsCloned] = true
|
|
867
|
-
location.schema = schemaClone
|
|
868
|
-
}
|
|
869
|
-
|
|
870
|
-
const schema = location.schema
|
|
871
|
-
const locations = []
|
|
872
|
-
|
|
873
|
-
schema[type].forEach((s, index) => {
|
|
874
|
-
// follow the refs
|
|
875
|
-
let sLocation = mergeLocation(location, { schema: s })
|
|
876
|
-
while (s.$ref) {
|
|
877
|
-
sLocation = refFinder(s.$ref, sLocation)
|
|
878
|
-
schema[type][index] = sLocation.schema
|
|
879
|
-
s = schema[type][index]
|
|
880
|
-
}
|
|
881
|
-
locations[index] = sLocation
|
|
882
|
-
})
|
|
883
|
-
|
|
884
|
-
return locations
|
|
885
|
-
}
|
|
886
|
-
|
|
887
761
|
let genFuncNameCounter = 0
|
|
888
762
|
function generateFuncName () {
|
|
889
763
|
return 'anonymous' + genFuncNameCounter++
|
|
890
764
|
}
|
|
891
765
|
|
|
892
|
-
function buildValue (
|
|
766
|
+
function buildValue (location, input) {
|
|
893
767
|
let schema = location.schema
|
|
894
768
|
|
|
895
769
|
if (schema.$ref) {
|
|
896
|
-
|
|
770
|
+
location = resolveRef(location, schema.$ref)
|
|
771
|
+
schema = location.schema
|
|
897
772
|
}
|
|
898
773
|
|
|
899
774
|
if (schema.type === undefined) {
|
|
@@ -940,41 +815,32 @@ function buildValue (locationPath, input, location) {
|
|
|
940
815
|
code += `json += ${funcName}(${input})`
|
|
941
816
|
break
|
|
942
817
|
case 'object':
|
|
943
|
-
funcName = buildObject(location
|
|
818
|
+
funcName = buildObject(location)
|
|
944
819
|
code += `json += ${funcName}(${input})`
|
|
945
820
|
break
|
|
946
821
|
case 'array':
|
|
947
|
-
funcName = buildArray(location
|
|
822
|
+
funcName = buildArray(location)
|
|
948
823
|
code += `json += ${funcName}(${input})`
|
|
949
824
|
break
|
|
950
825
|
case undefined:
|
|
951
|
-
if (schema.
|
|
826
|
+
if (schema.fjs_date_type) {
|
|
827
|
+
funcName = getStringSerializer(schema.fjs_date_type, nullable)
|
|
828
|
+
code += `json += ${funcName}(${input})`
|
|
829
|
+
break
|
|
830
|
+
} else if (schema.anyOf || schema.oneOf) {
|
|
952
831
|
// beware: dereferenceOfRefs has side effects and changes schema.anyOf
|
|
953
|
-
const
|
|
954
|
-
|
|
955
|
-
const nestedResult = buildValue(locationPath + 'i' + index, input, location)
|
|
956
|
-
// Since we are only passing the relevant schema to ajv.validate, it needs to be full dereferenced
|
|
957
|
-
// otherwise any $ref pointing to an external schema would result in an error.
|
|
958
|
-
// Full dereference of the schema happens as side effect of two functions:
|
|
959
|
-
// 1. `dereferenceOfRefs` loops through the `schema.anyOf`` array and replaces any top level reference
|
|
960
|
-
// with the actual schema
|
|
961
|
-
// 2. `buildValue`, through `buildCode`, replaces any reference in object properties with the actual schema
|
|
962
|
-
// (see https://github.com/fastify/fast-json-stringify/blob/6da3b3e8ac24b1ca5578223adedb4083b7adf8db/index.js#L631)
|
|
963
|
-
|
|
964
|
-
// Ajv does not support js date format. In order to properly validate objects containing a date,
|
|
965
|
-
// it needs to replace all occurrences of the string date format with a custom keyword fjs_date_type.
|
|
966
|
-
// (see https://github.com/fastify/fast-json-stringify/pull/441)
|
|
967
|
-
const extendedSchema = clone(location.schema)
|
|
968
|
-
extendDateTimeType(extendedSchema)
|
|
969
|
-
|
|
970
|
-
const schemaKey = location.schema.$id || randomUUID()
|
|
971
|
-
ajvInstance.addSchema(extendedSchema, schemaKey)
|
|
832
|
+
const type = schema.anyOf ? 'anyOf' : 'oneOf'
|
|
833
|
+
const anyOfLocation = mergeLocation(location, type)
|
|
972
834
|
|
|
835
|
+
for (let index = 0; index < location.schema[type].length; index++) {
|
|
836
|
+
const optionLocation = mergeLocation(anyOfLocation, index)
|
|
837
|
+
const schemaRef = optionLocation.schemaId + optionLocation.jsonPointer
|
|
838
|
+
const nestedResult = buildValue(optionLocation, input)
|
|
973
839
|
code += `
|
|
974
|
-
${index === 0 ? 'if' : 'else if'}(ajv.validate("${
|
|
840
|
+
${index === 0 ? 'if' : 'else if'}(ajv.validate("${schemaRef}", ${input}))
|
|
975
841
|
${nestedResult}
|
|
976
842
|
`
|
|
977
|
-
}
|
|
843
|
+
}
|
|
978
844
|
|
|
979
845
|
code += `
|
|
980
846
|
else throw new Error(\`The value $\{JSON.stringify(${input})} does not match schema definition.\`)
|
|
@@ -1011,10 +877,11 @@ function buildValue (locationPath, input, location) {
|
|
|
1011
877
|
} else {`
|
|
1012
878
|
}
|
|
1013
879
|
|
|
880
|
+
const locationClone = clone(location)
|
|
1014
881
|
sortedTypes.forEach((type, index) => {
|
|
1015
882
|
const statement = index === 0 ? 'if' : 'else if'
|
|
1016
|
-
|
|
1017
|
-
const nestedResult = buildValue(
|
|
883
|
+
locationClone.schema.type = type
|
|
884
|
+
const nestedResult = buildValue(locationClone, input)
|
|
1018
885
|
switch (type) {
|
|
1019
886
|
case 'string': {
|
|
1020
887
|
code += `
|
|
@@ -1063,7 +930,12 @@ function buildValue (locationPath, input, location) {
|
|
|
1063
930
|
return code
|
|
1064
931
|
}
|
|
1065
932
|
|
|
933
|
+
// Ajv does not support js date format. In order to properly validate objects containing a date,
|
|
934
|
+
// it needs to replace all occurrences of the string date format with a custom keyword fjs_date_type.
|
|
935
|
+
// (see https://github.com/fastify/fast-json-stringify/pull/441)
|
|
1066
936
|
function extendDateTimeType (schema) {
|
|
937
|
+
if (schema === null) return
|
|
938
|
+
|
|
1067
939
|
if (schema.type === 'string' && ['date-time', 'date', 'time'].includes(schema.format)) {
|
|
1068
940
|
schema.fjs_date_type = schema.format
|
|
1069
941
|
delete schema.type
|