fast-json-stringify 5.9.2 → 5.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/index.js +271 -237
- package/lib/location.js +1 -30
- package/lib/merge-schemas.js +9 -0
- package/package.json +4 -4
- package/test/allof.test.js +134 -17
- package/test/anyof.test.js +121 -0
- package/test/if-then-else.test.js +58 -9
- package/test/required.test.js +4 -3
- package/test/toJSON.test.js +33 -4
- package/test/typesArray.test.js +3 -2
package/README.md
CHANGED
|
@@ -590,6 +590,8 @@ Otherwise, instead of raising an error, null values will be coerced as follows:
|
|
|
590
590
|
- `number` -> `0`
|
|
591
591
|
- `string` -> `""`
|
|
592
592
|
- `boolean` -> `false`
|
|
593
|
+
- `object` -> `{}`
|
|
594
|
+
- `array` -> `[]`
|
|
593
595
|
|
|
594
596
|
<a name="largearrays"></a>
|
|
595
597
|
#### Large Arrays
|
package/index.js
CHANGED
|
@@ -2,14 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
/* eslint no-prototype-builtins: 0 */
|
|
4
4
|
|
|
5
|
-
const merge = require('@fastify/deepmerge')()
|
|
6
|
-
const clone = require('rfdc')({ proto: true })
|
|
7
5
|
const { RefResolver } = require('json-schema-ref-resolver')
|
|
8
6
|
|
|
9
|
-
const validate = require('./lib/schema-validator')
|
|
10
7
|
const Serializer = require('./lib/serializer')
|
|
11
8
|
const Validator = require('./lib/validator')
|
|
12
9
|
const Location = require('./lib/location')
|
|
10
|
+
const validate = require('./lib/schema-validator')
|
|
11
|
+
const mergeSchemas = require('./lib/merge-schemas')
|
|
13
12
|
|
|
14
13
|
const SINGLE_TICK = /'/g
|
|
15
14
|
|
|
@@ -28,10 +27,10 @@ const validLargeArrayMechanisms = [
|
|
|
28
27
|
'json-stringify'
|
|
29
28
|
]
|
|
30
29
|
|
|
31
|
-
const addComma = '!addComma && (addComma = true) || (json += \',\')'
|
|
32
|
-
|
|
33
30
|
let schemaIdCounter = 0
|
|
34
31
|
|
|
32
|
+
const mergedSchemaRef = Symbol('fjs-merged-schema-ref')
|
|
33
|
+
|
|
35
34
|
function isValidSchema (schema, name) {
|
|
36
35
|
if (!validate(schema)) {
|
|
37
36
|
if (name) {
|
|
@@ -46,13 +45,15 @@ function isValidSchema (schema, name) {
|
|
|
46
45
|
}
|
|
47
46
|
}
|
|
48
47
|
|
|
49
|
-
function resolveRef (context, location
|
|
48
|
+
function resolveRef (context, location) {
|
|
49
|
+
const ref = location.schema.$ref
|
|
50
|
+
|
|
50
51
|
let hashIndex = ref.indexOf('#')
|
|
51
52
|
if (hashIndex === -1) {
|
|
52
53
|
hashIndex = ref.length
|
|
53
54
|
}
|
|
54
55
|
|
|
55
|
-
const schemaId = ref.slice(0, hashIndex) || location.
|
|
56
|
+
const schemaId = ref.slice(0, hashIndex) || location.schemaId
|
|
56
57
|
const jsonPointer = ref.slice(hashIndex) || '#'
|
|
57
58
|
|
|
58
59
|
const schema = context.refResolver.getSchema(schemaId, jsonPointer)
|
|
@@ -62,12 +63,17 @@ function resolveRef (context, location, ref) {
|
|
|
62
63
|
|
|
63
64
|
const newLocation = new Location(schema, schemaId, jsonPointer)
|
|
64
65
|
if (schema.$ref !== undefined) {
|
|
65
|
-
return resolveRef(context, newLocation
|
|
66
|
+
return resolveRef(context, newLocation)
|
|
66
67
|
}
|
|
67
68
|
|
|
68
69
|
return newLocation
|
|
69
70
|
}
|
|
70
71
|
|
|
72
|
+
function getMergedLocation (context, mergedSchemaId) {
|
|
73
|
+
const mergedSchema = context.refResolver.getSchema(mergedSchemaId, '#')
|
|
74
|
+
return new Location(mergedSchema, mergedSchemaId, '#')
|
|
75
|
+
}
|
|
76
|
+
|
|
71
77
|
function getSchemaId (schema, rootSchemaId) {
|
|
72
78
|
if (schema.$id && schema.$id.charAt(0) !== '#') {
|
|
73
79
|
return schema.$id
|
|
@@ -85,7 +91,6 @@ function build (schema, options) {
|
|
|
85
91
|
functionsCounter: 0,
|
|
86
92
|
functionsNamesBySchema: new Map(),
|
|
87
93
|
options,
|
|
88
|
-
wrapObjects: true,
|
|
89
94
|
refResolver: new RefResolver(),
|
|
90
95
|
rootSchemaId: schema.$id || `__fjs_root_${schemaIdCounter++}`,
|
|
91
96
|
validatorSchemasIds: new Set()
|
|
@@ -255,7 +260,7 @@ function inferTypeByKeyword (schema) {
|
|
|
255
260
|
return schema.type
|
|
256
261
|
}
|
|
257
262
|
|
|
258
|
-
function buildExtraObjectPropertiesSerializer (context, location) {
|
|
263
|
+
function buildExtraObjectPropertiesSerializer (context, location, addComma) {
|
|
259
264
|
const schema = location.schema
|
|
260
265
|
const propertiesKeys = Object.keys(schema.properties || {})
|
|
261
266
|
|
|
@@ -314,225 +319,137 @@ function buildExtraObjectPropertiesSerializer (context, location) {
|
|
|
314
319
|
}
|
|
315
320
|
|
|
316
321
|
function buildInnerObject (context, location) {
|
|
317
|
-
let code = ''
|
|
318
322
|
const schema = location.schema
|
|
319
|
-
const required = schema.required || []
|
|
320
323
|
|
|
321
324
|
const propertiesLocation = location.getPropertyLocation('properties')
|
|
325
|
+
const requiredProperties = schema.required || []
|
|
322
326
|
|
|
323
|
-
|
|
324
|
-
const
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
propertyLocation = resolveRef(context, location, propertyLocation.schema.$ref)
|
|
333
|
-
}
|
|
327
|
+
// Should serialize required properties first
|
|
328
|
+
const propertiesKeys = Object.keys(schema.properties || {}).sort(
|
|
329
|
+
(key1, key2) => {
|
|
330
|
+
const required1 = requiredProperties.includes(key1)
|
|
331
|
+
const required2 = requiredProperties.includes(key2)
|
|
332
|
+
return required1 === required2 ? 0 : required1 ? -1 : 1
|
|
333
|
+
}
|
|
334
|
+
)
|
|
335
|
+
const hasRequiredProperties = requiredProperties.includes(propertiesKeys[0])
|
|
334
336
|
|
|
335
|
-
|
|
337
|
+
let code = ''
|
|
336
338
|
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
if (defaultValue === undefined) {
|
|
341
|
-
code += `if (obj[${sanitizedKey}] === undefined) throw new Error('${sanitizedKey} is required!')\n`
|
|
342
|
-
requiredWithoutDefault.push(key)
|
|
343
|
-
}
|
|
344
|
-
requiredWithDefault.push(key)
|
|
339
|
+
for (const key of requiredProperties) {
|
|
340
|
+
if (!propertiesKeys.includes(key)) {
|
|
341
|
+
code += `if (obj['${key}'] === undefined) throw new Error('"${key}" is required!')\n`
|
|
345
342
|
}
|
|
346
343
|
}
|
|
347
344
|
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
345
|
+
code += 'let json = \'{\'\n'
|
|
346
|
+
|
|
347
|
+
let addComma = ''
|
|
348
|
+
if (!hasRequiredProperties) {
|
|
349
|
+
code += 'let addComma = false\n'
|
|
350
|
+
addComma = '!addComma && (addComma = true) || (json += \',\')'
|
|
352
351
|
}
|
|
353
352
|
|
|
354
|
-
|
|
355
|
-
let
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
context.wrapObjects = true
|
|
360
|
-
|
|
361
|
-
if (schema.properties) {
|
|
362
|
-
for (const key of Object.keys(schema.properties)) {
|
|
363
|
-
let propertyLocation = propertiesLocation.getPropertyLocation(key)
|
|
364
|
-
if (propertyLocation.schema.$ref) {
|
|
365
|
-
propertyLocation = resolveRef(context, location, propertyLocation.schema.$ref)
|
|
366
|
-
}
|
|
353
|
+
for (const key of propertiesKeys) {
|
|
354
|
+
let propertyLocation = propertiesLocation.getPropertyLocation(key)
|
|
355
|
+
if (propertyLocation.schema.$ref) {
|
|
356
|
+
propertyLocation = resolveRef(context, propertyLocation)
|
|
357
|
+
}
|
|
367
358
|
|
|
368
|
-
|
|
359
|
+
const sanitizedKey = JSON.stringify(key)
|
|
360
|
+
const defaultValue = propertyLocation.schema.default
|
|
361
|
+
const isRequired = requiredProperties.includes(key)
|
|
369
362
|
|
|
370
|
-
|
|
371
|
-
|
|
363
|
+
code += `
|
|
364
|
+
if (obj[${sanitizedKey}] !== undefined) {
|
|
372
365
|
${addComma}
|
|
373
366
|
json += ${JSON.stringify(sanitizedKey + ':')}
|
|
374
367
|
${buildValue(context, propertyLocation, `obj[${sanitizedKey}]`)}
|
|
368
|
+
}`
|
|
369
|
+
|
|
370
|
+
if (defaultValue !== undefined) {
|
|
371
|
+
code += ` else {
|
|
372
|
+
${addComma}
|
|
373
|
+
json += ${JSON.stringify(sanitizedKey + ':' + JSON.stringify(defaultValue))}
|
|
374
|
+
}
|
|
375
375
|
`
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
code += `
|
|
380
|
-
if (obj[${sanitizedKey}] !== undefined) {
|
|
381
|
-
${addComma}
|
|
382
|
-
json += ${JSON.stringify(sanitizedKey + ':')}
|
|
383
|
-
${buildValue(context, propertyLocation, `obj[${sanitizedKey}]`)}
|
|
384
|
-
}
|
|
385
|
-
`
|
|
386
|
-
const defaultValue = propertyLocation.schema.default
|
|
387
|
-
if (defaultValue !== undefined) {
|
|
388
|
-
code += `
|
|
389
|
-
else {
|
|
390
|
-
${addComma}
|
|
391
|
-
json += ${JSON.stringify(sanitizedKey + ':' + JSON.stringify(defaultValue))}
|
|
392
|
-
}
|
|
393
|
-
`
|
|
394
|
-
}
|
|
376
|
+
} else if (isRequired) {
|
|
377
|
+
code += ` else {
|
|
378
|
+
throw new Error('${sanitizedKey} is required!')
|
|
395
379
|
}
|
|
380
|
+
`
|
|
381
|
+
} else {
|
|
382
|
+
code += '\n'
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
if (hasRequiredProperties) {
|
|
386
|
+
addComma = 'json += \',\''
|
|
396
387
|
}
|
|
397
388
|
}
|
|
398
389
|
|
|
399
390
|
if (schema.patternProperties || schema.additionalProperties) {
|
|
400
|
-
code += buildExtraObjectPropertiesSerializer(context, location)
|
|
391
|
+
code += buildExtraObjectPropertiesSerializer(context, location, addComma)
|
|
401
392
|
}
|
|
402
393
|
|
|
403
|
-
context.wrapObjects = wrapObjects
|
|
404
394
|
code += `
|
|
405
|
-
|
|
395
|
+
return json + '}'
|
|
406
396
|
`
|
|
407
397
|
return code
|
|
408
398
|
}
|
|
409
399
|
|
|
410
|
-
function
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
if (allOfSchema.$ref) {
|
|
417
|
-
const allOfSchemaLocation = allOfLocation.getPropertyLocation(i)
|
|
418
|
-
allOfSchema = resolveRef(context, allOfSchemaLocation, allOfSchema.$ref).schema
|
|
400
|
+
function mergeLocations (context, mergedSchemaId, mergedLocations) {
|
|
401
|
+
for (let i = 0; i < mergedLocations.length; i++) {
|
|
402
|
+
const location = mergedLocations[i]
|
|
403
|
+
const schema = location.schema
|
|
404
|
+
if (schema.$ref) {
|
|
405
|
+
mergedLocations[i] = resolveRef(context, location)
|
|
419
406
|
}
|
|
407
|
+
}
|
|
420
408
|
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
409
|
+
const mergedSchemas = []
|
|
410
|
+
for (const location of mergedLocations) {
|
|
411
|
+
const schema = cloneOriginSchema(location.schema, location.schemaId)
|
|
412
|
+
delete schema.$id
|
|
425
413
|
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
mergedSchema.type !== undefined &&
|
|
429
|
-
mergedSchema.type !== allOfSchemaType
|
|
430
|
-
) {
|
|
431
|
-
throw new Error('allOf schemas have different type values')
|
|
432
|
-
}
|
|
433
|
-
mergedSchema.type = allOfSchemaType
|
|
434
|
-
}
|
|
414
|
+
mergedSchemas.push(schema)
|
|
415
|
+
}
|
|
435
416
|
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
mergedSchema.format !== undefined &&
|
|
439
|
-
mergedSchema.format !== allOfSchema.format
|
|
440
|
-
) {
|
|
441
|
-
throw new Error('allOf schemas have different format values')
|
|
442
|
-
}
|
|
443
|
-
mergedSchema.format = allOfSchema.format
|
|
444
|
-
}
|
|
417
|
+
const mergedSchema = mergeSchemas(mergedSchemas)
|
|
418
|
+
const mergedLocation = new Location(mergedSchema, mergedSchemaId)
|
|
445
419
|
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
mergedSchema.nullable !== allOfSchema.nullable
|
|
450
|
-
) {
|
|
451
|
-
throw new Error('allOf schemas have different nullable values')
|
|
452
|
-
}
|
|
453
|
-
mergedSchema.nullable = allOfSchema.nullable
|
|
454
|
-
}
|
|
420
|
+
context.refResolver.addSchema(mergedSchema, mergedSchemaId)
|
|
421
|
+
return mergedLocation
|
|
422
|
+
}
|
|
455
423
|
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
mergedSchema.properties = {}
|
|
459
|
-
}
|
|
460
|
-
Object.assign(mergedSchema.properties, allOfSchema.properties)
|
|
461
|
-
}
|
|
424
|
+
function cloneOriginSchema (schema, schemaId) {
|
|
425
|
+
const clonedSchema = Array.isArray(schema) ? [] : {}
|
|
462
426
|
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
427
|
+
if (
|
|
428
|
+
schema.$id !== undefined &&
|
|
429
|
+
schema.$id.charAt(0) !== '#'
|
|
430
|
+
) {
|
|
431
|
+
schemaId = schema.$id
|
|
432
|
+
}
|
|
469
433
|
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
}
|
|
474
|
-
Object.assign(mergedSchema.patternProperties, allOfSchema.patternProperties)
|
|
475
|
-
}
|
|
434
|
+
if (schema[mergedSchemaRef]) {
|
|
435
|
+
clonedSchema[mergedSchemaRef] = schema[mergedSchemaRef]
|
|
436
|
+
}
|
|
476
437
|
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
mergedSchema.required = []
|
|
480
|
-
}
|
|
481
|
-
mergedSchema.required.push(...allOfSchema.required)
|
|
482
|
-
}
|
|
438
|
+
for (const key in schema) {
|
|
439
|
+
let value = schema[key]
|
|
483
440
|
|
|
484
|
-
if (
|
|
485
|
-
|
|
486
|
-
mergedSchema.oneOf = []
|
|
487
|
-
}
|
|
488
|
-
mergedSchema.oneOf.push(...allOfSchema.oneOf)
|
|
441
|
+
if (key === '$ref' && value.charAt(0) === '#') {
|
|
442
|
+
value = schemaId + value
|
|
489
443
|
}
|
|
490
444
|
|
|
491
|
-
if (
|
|
492
|
-
|
|
493
|
-
mergedSchema.anyOf = []
|
|
494
|
-
}
|
|
495
|
-
mergedSchema.anyOf.push(...allOfSchema.anyOf)
|
|
445
|
+
if (typeof value === 'object' && value !== null) {
|
|
446
|
+
value = cloneOriginSchema(value, schemaId)
|
|
496
447
|
}
|
|
497
448
|
|
|
498
|
-
|
|
499
|
-
mergeAllOfSchema(context, location, allOfSchema, mergedSchema)
|
|
500
|
-
}
|
|
449
|
+
clonedSchema[key] = value
|
|
501
450
|
}
|
|
502
|
-
delete mergedSchema.allOf
|
|
503
|
-
|
|
504
|
-
mergedSchema.$id = `__fjs_merged_${schemaIdCounter++}`
|
|
505
|
-
context.refResolver.addSchema(mergedSchema)
|
|
506
|
-
location.addMergedSchema(mergedSchema, mergedSchema.$id)
|
|
507
|
-
}
|
|
508
|
-
|
|
509
|
-
function addIfThenElse (context, location, input) {
|
|
510
|
-
context.validatorSchemasIds.add(location.getSchemaId())
|
|
511
|
-
|
|
512
|
-
const schema = merge({}, location.schema)
|
|
513
|
-
const thenSchema = schema.then
|
|
514
|
-
const elseSchema = schema.else || { additionalProperties: true }
|
|
515
451
|
|
|
516
|
-
|
|
517
|
-
delete schema.then
|
|
518
|
-
delete schema.else
|
|
519
|
-
|
|
520
|
-
const ifLocation = location.getPropertyLocation('if')
|
|
521
|
-
const ifSchemaRef = ifLocation.getSchemaRef()
|
|
522
|
-
|
|
523
|
-
const thenLocation = location.getPropertyLocation('then')
|
|
524
|
-
thenLocation.schema = merge(schema, thenSchema)
|
|
525
|
-
|
|
526
|
-
const elseLocation = location.getPropertyLocation('else')
|
|
527
|
-
elseLocation.schema = merge(schema, elseSchema)
|
|
528
|
-
|
|
529
|
-
return `
|
|
530
|
-
if (validator.validate("${ifSchemaRef}", ${input})) {
|
|
531
|
-
${buildValue(context, thenLocation, input)}
|
|
532
|
-
} else {
|
|
533
|
-
${buildValue(context, elseLocation, input)}
|
|
534
|
-
}
|
|
535
|
-
`
|
|
452
|
+
return clonedSchema
|
|
536
453
|
}
|
|
537
454
|
|
|
538
455
|
function toJSON (variableName) {
|
|
@@ -560,10 +477,13 @@ function buildObject (context, location) {
|
|
|
560
477
|
let functionCode = `
|
|
561
478
|
`
|
|
562
479
|
|
|
480
|
+
const nullable = schema.nullable === true
|
|
563
481
|
functionCode += `
|
|
564
482
|
// ${schemaRef}
|
|
565
483
|
function ${functionName} (input) {
|
|
566
484
|
const obj = ${toJSON('input')}
|
|
485
|
+
${!nullable ? 'if (obj === null) return \'{}\'' : ''}
|
|
486
|
+
|
|
567
487
|
${buildInnerObject(context, location)}
|
|
568
488
|
}
|
|
569
489
|
`
|
|
@@ -579,7 +499,7 @@ function buildArray (context, location) {
|
|
|
579
499
|
itemsLocation.schema = itemsLocation.schema || {}
|
|
580
500
|
|
|
581
501
|
if (itemsLocation.schema.$ref) {
|
|
582
|
-
itemsLocation = resolveRef(context, itemsLocation
|
|
502
|
+
itemsLocation = resolveRef(context, itemsLocation)
|
|
583
503
|
}
|
|
584
504
|
|
|
585
505
|
const itemsSchema = itemsLocation.schema
|
|
@@ -601,7 +521,9 @@ function buildArray (context, location) {
|
|
|
601
521
|
// ${schemaRef}
|
|
602
522
|
`
|
|
603
523
|
|
|
524
|
+
const nullable = schema.nullable === true
|
|
604
525
|
functionCode += `
|
|
526
|
+
${!nullable ? 'if (obj === null) return \'[]\'' : ''}
|
|
605
527
|
if (!Array.isArray(obj)) {
|
|
606
528
|
throw new TypeError(\`The value of '${schemaRef}' does not match schema definition.\`)
|
|
607
529
|
}
|
|
@@ -855,81 +777,193 @@ function buildConstSerializer (location, input) {
|
|
|
855
777
|
return code
|
|
856
778
|
}
|
|
857
779
|
|
|
858
|
-
function
|
|
859
|
-
|
|
780
|
+
function buildAllOf (context, location, input) {
|
|
781
|
+
const schema = location.schema
|
|
860
782
|
|
|
861
|
-
|
|
862
|
-
|
|
783
|
+
let mergedSchemaId = schema[mergedSchemaRef]
|
|
784
|
+
if (mergedSchemaId) {
|
|
785
|
+
const mergedLocation = getMergedLocation(context, mergedSchemaId)
|
|
786
|
+
return buildValue(context, mergedLocation, input)
|
|
863
787
|
}
|
|
864
788
|
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
789
|
+
mergedSchemaId = `__fjs_merged_${schemaIdCounter++}`
|
|
790
|
+
schema[mergedSchemaRef] = mergedSchemaId
|
|
791
|
+
|
|
792
|
+
const { allOf, ...schemaWithoutAllOf } = location.schema
|
|
793
|
+
const locations = [
|
|
794
|
+
new Location(
|
|
795
|
+
schemaWithoutAllOf,
|
|
796
|
+
location.schemaId,
|
|
797
|
+
location.jsonPointer
|
|
798
|
+
)
|
|
799
|
+
]
|
|
800
|
+
|
|
801
|
+
const allOfsLocation = location.getPropertyLocation('allOf')
|
|
802
|
+
for (let i = 0; i < allOf.length; i++) {
|
|
803
|
+
locations.push(allOfsLocation.getPropertyLocation(i))
|
|
868
804
|
}
|
|
869
805
|
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
806
|
+
const mergedLocation = mergeLocations(context, mergedSchemaId, locations)
|
|
807
|
+
return buildValue(context, mergedLocation, input)
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
function buildOneOf (context, location, input) {
|
|
811
|
+
context.validatorSchemasIds.add(location.schemaId)
|
|
812
|
+
|
|
813
|
+
const schema = location.schema
|
|
814
|
+
|
|
815
|
+
const type = schema.anyOf ? 'anyOf' : 'oneOf'
|
|
816
|
+
const { [type]: oneOfs, ...schemaWithoutAnyOf } = location.schema
|
|
817
|
+
|
|
818
|
+
const locationWithoutOneOf = new Location(
|
|
819
|
+
schemaWithoutAnyOf,
|
|
820
|
+
location.schemaId,
|
|
821
|
+
location.jsonPointer
|
|
822
|
+
)
|
|
823
|
+
const oneOfsLocation = location.getPropertyLocation(type)
|
|
824
|
+
|
|
825
|
+
let code = ''
|
|
826
|
+
|
|
827
|
+
for (let index = 0; index < oneOfs.length; index++) {
|
|
828
|
+
const optionLocation = oneOfsLocation.getPropertyLocation(index)
|
|
829
|
+
const optionSchema = optionLocation.schema
|
|
830
|
+
|
|
831
|
+
let mergedSchemaId = optionSchema[mergedSchemaRef]
|
|
832
|
+
let mergedLocation = null
|
|
833
|
+
if (mergedSchemaId) {
|
|
834
|
+
mergedLocation = getMergedLocation(context, mergedSchemaId)
|
|
835
|
+
} else {
|
|
836
|
+
mergedSchemaId = `__fjs_merged_${schemaIdCounter++}`
|
|
837
|
+
optionSchema[mergedSchemaRef] = mergedSchemaId
|
|
838
|
+
|
|
839
|
+
mergedLocation = mergeLocations(context, mergedSchemaId, [
|
|
840
|
+
locationWithoutOneOf,
|
|
841
|
+
optionLocation
|
|
842
|
+
])
|
|
874
843
|
}
|
|
844
|
+
|
|
845
|
+
const nestedResult = buildValue(context, mergedLocation, input)
|
|
846
|
+
const schemaRef = optionLocation.getSchemaRef()
|
|
847
|
+
code += `
|
|
848
|
+
${index === 0 ? 'if' : 'else if'}(validator.validate("${schemaRef}", ${input}))
|
|
849
|
+
${nestedResult}
|
|
850
|
+
`
|
|
875
851
|
}
|
|
876
852
|
|
|
877
|
-
|
|
878
|
-
|
|
853
|
+
let schemaRef = location.getSchemaRef()
|
|
854
|
+
if (schemaRef.startsWith(context.rootSchemaId)) {
|
|
855
|
+
schemaRef = schemaRef.replace(context.rootSchemaId, '')
|
|
879
856
|
}
|
|
880
857
|
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
858
|
+
code += `
|
|
859
|
+
else throw new TypeError(\`The value of '${schemaRef}' does not match schema definition.\`)
|
|
860
|
+
`
|
|
861
|
+
|
|
862
|
+
return code
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
function buildIfThenElse (context, location, input) {
|
|
866
|
+
context.validatorSchemasIds.add(location.schemaId)
|
|
867
|
+
|
|
868
|
+
const {
|
|
869
|
+
if: ifSchema,
|
|
870
|
+
then: thenSchema,
|
|
871
|
+
else: elseSchema,
|
|
872
|
+
...schemaWithoutIfThenElse
|
|
873
|
+
} = location.schema
|
|
874
|
+
|
|
875
|
+
const rootLocation = new Location(
|
|
876
|
+
schemaWithoutIfThenElse,
|
|
877
|
+
location.schemaId,
|
|
878
|
+
location.jsonPointer
|
|
879
|
+
)
|
|
880
|
+
|
|
881
|
+
const ifLocation = location.getPropertyLocation('if')
|
|
882
|
+
const ifSchemaRef = ifLocation.getSchemaRef()
|
|
883
|
+
|
|
884
|
+
const thenLocation = location.getPropertyLocation('then')
|
|
885
|
+
let thenMergedSchemaId = thenSchema[mergedSchemaRef]
|
|
886
|
+
let thenMergedLocation = null
|
|
887
|
+
if (thenMergedSchemaId) {
|
|
888
|
+
thenMergedLocation = getMergedLocation(context, thenMergedSchemaId)
|
|
889
|
+
} else {
|
|
890
|
+
thenMergedSchemaId = `__fjs_merged_${schemaIdCounter++}`
|
|
891
|
+
thenSchema[mergedSchemaRef] = thenMergedSchemaId
|
|
892
|
+
|
|
893
|
+
thenMergedLocation = mergeLocations(context, thenMergedSchemaId, [
|
|
894
|
+
rootLocation,
|
|
895
|
+
thenLocation
|
|
896
|
+
])
|
|
884
897
|
}
|
|
885
898
|
|
|
886
|
-
|
|
899
|
+
if (!elseSchema) {
|
|
900
|
+
return `
|
|
901
|
+
if (validator.validate("${ifSchemaRef}", ${input})) {
|
|
902
|
+
${buildValue(context, thenMergedLocation, input)}
|
|
903
|
+
} else {
|
|
904
|
+
${buildValue(context, rootLocation, input)}
|
|
905
|
+
}
|
|
906
|
+
`
|
|
907
|
+
}
|
|
887
908
|
|
|
888
|
-
|
|
909
|
+
const elseLocation = location.getPropertyLocation('else')
|
|
910
|
+
let elseMergedSchemaId = elseSchema[mergedSchemaRef]
|
|
911
|
+
let elseMergedLocation = null
|
|
912
|
+
if (elseMergedSchemaId) {
|
|
913
|
+
elseMergedLocation = getMergedLocation(context, elseMergedSchemaId)
|
|
914
|
+
} else {
|
|
915
|
+
elseMergedSchemaId = `__fjs_merged_${schemaIdCounter++}`
|
|
916
|
+
elseSchema[mergedSchemaRef] = elseMergedSchemaId
|
|
889
917
|
|
|
890
|
-
|
|
891
|
-
|
|
918
|
+
elseMergedLocation = mergeLocations(context, elseMergedSchemaId, [
|
|
919
|
+
rootLocation,
|
|
920
|
+
elseLocation
|
|
921
|
+
])
|
|
922
|
+
}
|
|
892
923
|
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
json += ${funcName}(${input})
|
|
899
|
-
json += ','
|
|
900
|
-
`
|
|
924
|
+
return `
|
|
925
|
+
if (validator.validate("${ifSchemaRef}", ${input})) {
|
|
926
|
+
${buildValue(context, thenMergedLocation, input)}
|
|
927
|
+
} else {
|
|
928
|
+
${buildValue(context, elseMergedLocation, input)}
|
|
901
929
|
}
|
|
930
|
+
`
|
|
931
|
+
}
|
|
902
932
|
|
|
903
|
-
|
|
904
|
-
|
|
933
|
+
function buildValue (context, location, input) {
|
|
934
|
+
let schema = location.schema
|
|
905
935
|
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
const nestedResult = buildValue(context, optionLocation, input)
|
|
910
|
-
code += `
|
|
911
|
-
${index === 0 ? 'if' : 'else if'}(validator.validate("${schemaRef}", ${input}))
|
|
912
|
-
${nestedResult}
|
|
913
|
-
`
|
|
914
|
-
}
|
|
936
|
+
if (typeof schema === 'boolean') {
|
|
937
|
+
return `json += JSON.stringify(${input})`
|
|
938
|
+
}
|
|
915
939
|
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
940
|
+
if (schema.$ref) {
|
|
941
|
+
location = resolveRef(context, location)
|
|
942
|
+
schema = location.schema
|
|
943
|
+
}
|
|
920
944
|
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
945
|
+
if (schema.allOf) {
|
|
946
|
+
return buildAllOf(context, location, input)
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
if (schema.anyOf || schema.oneOf) {
|
|
950
|
+
return buildOneOf(context, location, input)
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
if (schema.if && schema.then) {
|
|
954
|
+
return buildIfThenElse(context, location, input)
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
if (schema.type === undefined) {
|
|
958
|
+
const inferredType = inferTypeByKeyword(schema)
|
|
959
|
+
if (inferredType) {
|
|
960
|
+
schema.type = inferredType
|
|
929
961
|
}
|
|
930
|
-
return code
|
|
931
962
|
}
|
|
932
963
|
|
|
964
|
+
let code = ''
|
|
965
|
+
|
|
966
|
+
const type = schema.type
|
|
933
967
|
const nullable = schema.nullable === true
|
|
934
968
|
if (nullable) {
|
|
935
969
|
code += `
|
package/lib/location.js
CHANGED
|
@@ -5,7 +5,6 @@ class Location {
|
|
|
5
5
|
this.schema = schema
|
|
6
6
|
this.schemaId = schemaId
|
|
7
7
|
this.jsonPointer = jsonPointer
|
|
8
|
-
this.mergedSchemaId = null
|
|
9
8
|
}
|
|
10
9
|
|
|
11
10
|
getPropertyLocation (propertyName) {
|
|
@@ -14,39 +13,11 @@ class Location {
|
|
|
14
13
|
this.schemaId,
|
|
15
14
|
this.jsonPointer + '/' + propertyName
|
|
16
15
|
)
|
|
17
|
-
|
|
18
|
-
if (this.mergedSchemaId !== null) {
|
|
19
|
-
propertyLocation.addMergedSchema(
|
|
20
|
-
this.schema[propertyName],
|
|
21
|
-
this.mergedSchemaId,
|
|
22
|
-
this.jsonPointer + '/' + propertyName
|
|
23
|
-
)
|
|
24
|
-
}
|
|
25
|
-
|
|
26
16
|
return propertyLocation
|
|
27
17
|
}
|
|
28
18
|
|
|
29
|
-
// Use this method to get current schema location.
|
|
30
|
-
// Use it when you need to create reference to the current location.
|
|
31
|
-
getSchemaId () {
|
|
32
|
-
return this.mergedSchemaId || this.schemaId
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
// Use this method to get original schema id for resolving user schema $refs
|
|
36
|
-
// Don't join it with a JSON pointer to get the current location.
|
|
37
|
-
getOriginSchemaId () {
|
|
38
|
-
return this.schemaId
|
|
39
|
-
}
|
|
40
|
-
|
|
41
19
|
getSchemaRef () {
|
|
42
|
-
|
|
43
|
-
return schemaId + this.jsonPointer
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
addMergedSchema (mergedSchema, schemaId, jsonPointer = '#') {
|
|
47
|
-
this.schema = mergedSchema
|
|
48
|
-
this.mergedSchemaId = schemaId
|
|
49
|
-
this.jsonPointer = jsonPointer
|
|
20
|
+
return this.schemaId + this.jsonPointer
|
|
50
21
|
}
|
|
51
22
|
}
|
|
52
23
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fast-json-stringify",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.11.0",
|
|
4
4
|
"description": "Stringify your JSON at max speed",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "commonjs",
|
|
@@ -46,18 +46,18 @@
|
|
|
46
46
|
"simple-git": "^3.7.1",
|
|
47
47
|
"standard": "^17.0.0",
|
|
48
48
|
"tap": "^16.0.1",
|
|
49
|
-
"tsd": "^0.
|
|
49
|
+
"tsd": "^0.30.3",
|
|
50
50
|
"webpack": "^5.40.0",
|
|
51
51
|
"fast-json-stringify": "."
|
|
52
52
|
},
|
|
53
53
|
"dependencies": {
|
|
54
|
-
"@fastify/deepmerge": "^1.0.0",
|
|
55
54
|
"ajv": "^8.10.0",
|
|
56
55
|
"ajv-formats": "^2.1.1",
|
|
57
56
|
"fast-deep-equal": "^3.1.3",
|
|
58
57
|
"fast-uri": "^2.1.0",
|
|
59
58
|
"rfdc": "^1.2.0",
|
|
60
|
-
"json-schema-ref-resolver": "^1.0.1"
|
|
59
|
+
"json-schema-ref-resolver": "^1.0.1",
|
|
60
|
+
"@fastify/merge-json-schemas": "^0.1.0"
|
|
61
61
|
},
|
|
62
62
|
"standard": {
|
|
63
63
|
"ignore": [
|
package/test/allof.test.js
CHANGED
|
@@ -121,7 +121,7 @@ test('object with allOf and multiple schema on the allOf', (t) => {
|
|
|
121
121
|
id: 1,
|
|
122
122
|
name: 'string',
|
|
123
123
|
tag: 'otherString'
|
|
124
|
-
}), '{"name":"string","tag":"otherString"
|
|
124
|
+
}), '{"name":"string","id":1,"tag":"otherString"}')
|
|
125
125
|
})
|
|
126
126
|
|
|
127
127
|
test('object with allOf and one schema on the allOf', (t) => {
|
|
@@ -553,8 +553,60 @@ test('allof with local anchor reference', (t) => {
|
|
|
553
553
|
t.equal(stringify(data), JSON.stringify(data))
|
|
554
554
|
})
|
|
555
555
|
|
|
556
|
+
test('allOf: multiple nested $ref properties', (t) => {
|
|
557
|
+
t.plan(2)
|
|
558
|
+
|
|
559
|
+
const externalSchema1 = {
|
|
560
|
+
$id: 'externalSchema1',
|
|
561
|
+
oneOf: [
|
|
562
|
+
{ $ref: '#/definitions/id1' }
|
|
563
|
+
],
|
|
564
|
+
definitions: {
|
|
565
|
+
id1: {
|
|
566
|
+
type: 'object',
|
|
567
|
+
properties: {
|
|
568
|
+
id1: {
|
|
569
|
+
type: 'integer'
|
|
570
|
+
}
|
|
571
|
+
},
|
|
572
|
+
additionalProperties: false
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
const externalSchema2 = {
|
|
578
|
+
$id: 'externalSchema2',
|
|
579
|
+
oneOf: [
|
|
580
|
+
{ $ref: '#/definitions/id2' }
|
|
581
|
+
],
|
|
582
|
+
definitions: {
|
|
583
|
+
id2: {
|
|
584
|
+
type: 'object',
|
|
585
|
+
properties: {
|
|
586
|
+
id2: {
|
|
587
|
+
type: 'integer'
|
|
588
|
+
}
|
|
589
|
+
},
|
|
590
|
+
additionalProperties: false
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
const schema = {
|
|
596
|
+
anyOf: [
|
|
597
|
+
{ $ref: 'externalSchema1' },
|
|
598
|
+
{ $ref: 'externalSchema2' }
|
|
599
|
+
]
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
const stringify = build(schema, { schema: [externalSchema1, externalSchema2] })
|
|
603
|
+
|
|
604
|
+
t.equal(stringify({ id1: 1 }), JSON.stringify({ id1: 1 }))
|
|
605
|
+
t.equal(stringify({ id2: 2 }), JSON.stringify({ id2: 2 }))
|
|
606
|
+
})
|
|
607
|
+
|
|
556
608
|
test('allOf: throw Error if types mismatch ', (t) => {
|
|
557
|
-
t.plan(
|
|
609
|
+
t.plan(3)
|
|
558
610
|
|
|
559
611
|
const schema = {
|
|
560
612
|
allOf: [
|
|
@@ -562,11 +614,18 @@ test('allOf: throw Error if types mismatch ', (t) => {
|
|
|
562
614
|
{ type: 'number' }
|
|
563
615
|
]
|
|
564
616
|
}
|
|
565
|
-
|
|
617
|
+
try {
|
|
618
|
+
build(schema)
|
|
619
|
+
t.fail('should throw the MergeError')
|
|
620
|
+
} catch (error) {
|
|
621
|
+
t.ok(error instanceof Error)
|
|
622
|
+
t.equal(error.message, 'Failed to merge "type" keyword schemas.')
|
|
623
|
+
t.same(error.schemas, [['string'], ['number']])
|
|
624
|
+
}
|
|
566
625
|
})
|
|
567
626
|
|
|
568
627
|
test('allOf: throw Error if format mismatch ', (t) => {
|
|
569
|
-
t.plan(
|
|
628
|
+
t.plan(3)
|
|
570
629
|
|
|
571
630
|
const schema = {
|
|
572
631
|
allOf: [
|
|
@@ -574,29 +633,87 @@ test('allOf: throw Error if format mismatch ', (t) => {
|
|
|
574
633
|
{ format: 'time' }
|
|
575
634
|
]
|
|
576
635
|
}
|
|
577
|
-
|
|
636
|
+
try {
|
|
637
|
+
build(schema)
|
|
638
|
+
t.fail('should throw the MergeError')
|
|
639
|
+
} catch (error) {
|
|
640
|
+
t.ok(error instanceof Error)
|
|
641
|
+
t.equal(error.message, 'Failed to merge "format" keyword schemas.')
|
|
642
|
+
t.same(error.schemas, ['date', 'time'])
|
|
643
|
+
}
|
|
578
644
|
})
|
|
579
645
|
|
|
580
|
-
test('
|
|
646
|
+
test('recursive nested allOfs', (t) => {
|
|
581
647
|
t.plan(1)
|
|
582
648
|
|
|
583
649
|
const schema = {
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
650
|
+
type: 'object',
|
|
651
|
+
properties: {
|
|
652
|
+
foo: {
|
|
653
|
+
additionalProperties: false,
|
|
654
|
+
allOf: [{ $ref: '#' }]
|
|
655
|
+
}
|
|
656
|
+
}
|
|
588
657
|
}
|
|
589
|
-
|
|
658
|
+
|
|
659
|
+
const data = { foo: {} }
|
|
660
|
+
const stringify = build(schema)
|
|
661
|
+
t.equal(stringify(data), JSON.stringify(data))
|
|
590
662
|
})
|
|
591
663
|
|
|
592
|
-
test('
|
|
664
|
+
test('recursive nested allOfs', (t) => {
|
|
593
665
|
t.plan(1)
|
|
594
666
|
|
|
595
667
|
const schema = {
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
668
|
+
type: 'object',
|
|
669
|
+
properties: {
|
|
670
|
+
foo: {
|
|
671
|
+
additionalProperties: false,
|
|
672
|
+
allOf: [{ allOf: [{ $ref: '#' }] }]
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
const data = { foo: {} }
|
|
678
|
+
const stringify = build(schema)
|
|
679
|
+
t.equal(stringify(data), JSON.stringify(data))
|
|
680
|
+
})
|
|
681
|
+
|
|
682
|
+
test('external recursive allOfs', (t) => {
|
|
683
|
+
t.plan(1)
|
|
684
|
+
|
|
685
|
+
const externalSchema = {
|
|
686
|
+
type: 'object',
|
|
687
|
+
properties: {
|
|
688
|
+
foo: {
|
|
689
|
+
properties: {
|
|
690
|
+
bar: { type: 'string' }
|
|
691
|
+
},
|
|
692
|
+
allOf: [{ $ref: '#' }]
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
const schema = {
|
|
698
|
+
type: 'object',
|
|
699
|
+
properties: {
|
|
700
|
+
a: { $ref: 'externalSchema#/properties/foo' },
|
|
701
|
+
b: { $ref: 'externalSchema#/properties/foo' }
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
const data = {
|
|
706
|
+
a: {
|
|
707
|
+
foo: {},
|
|
708
|
+
bar: '42',
|
|
709
|
+
baz: 42
|
|
710
|
+
},
|
|
711
|
+
b: {
|
|
712
|
+
foo: {},
|
|
713
|
+
bar: '42',
|
|
714
|
+
baz: 42
|
|
715
|
+
}
|
|
600
716
|
}
|
|
601
|
-
|
|
717
|
+
const stringify = build(schema, { schema: { externalSchema } })
|
|
718
|
+
t.equal(stringify(data), '{"a":{"bar":"42","foo":{}},"b":{"bar":"42","foo":{}}}')
|
|
602
719
|
})
|
package/test/anyof.test.js
CHANGED
|
@@ -644,3 +644,124 @@ test('object with ref and validated properties', (t) => {
|
|
|
644
644
|
const stringify = build(schema, { schema: externalSchemas })
|
|
645
645
|
t.equal(stringify({ id: 1, reference: 'hi' }), '{"id":1,"reference":"hi"}')
|
|
646
646
|
})
|
|
647
|
+
|
|
648
|
+
test('anyOf required props', (t) => {
|
|
649
|
+
t.plan(3)
|
|
650
|
+
|
|
651
|
+
const schema = {
|
|
652
|
+
type: 'object',
|
|
653
|
+
properties: {
|
|
654
|
+
prop1: { type: 'string' },
|
|
655
|
+
prop2: { type: 'string' },
|
|
656
|
+
prop3: { type: 'string' }
|
|
657
|
+
},
|
|
658
|
+
required: ['prop1'],
|
|
659
|
+
anyOf: [{ required: ['prop2'] }, { required: ['prop3'] }]
|
|
660
|
+
}
|
|
661
|
+
const stringify = build(schema)
|
|
662
|
+
t.equal(stringify({ prop1: 'test', prop2: 'test2' }), '{"prop1":"test","prop2":"test2"}')
|
|
663
|
+
t.equal(stringify({ prop1: 'test', prop3: 'test3' }), '{"prop1":"test","prop3":"test3"}')
|
|
664
|
+
t.equal(stringify({ prop1: 'test', prop2: 'test2', prop3: 'test3' }), '{"prop1":"test","prop2":"test2","prop3":"test3"}')
|
|
665
|
+
})
|
|
666
|
+
|
|
667
|
+
test('anyOf required props', (t) => {
|
|
668
|
+
t.plan(3)
|
|
669
|
+
|
|
670
|
+
const schema = {
|
|
671
|
+
type: 'object',
|
|
672
|
+
properties: {
|
|
673
|
+
prop1: { type: 'string' }
|
|
674
|
+
},
|
|
675
|
+
anyOf: [
|
|
676
|
+
{
|
|
677
|
+
properties: {
|
|
678
|
+
prop2: { type: 'string' }
|
|
679
|
+
}
|
|
680
|
+
},
|
|
681
|
+
{
|
|
682
|
+
properties: {
|
|
683
|
+
prop3: { type: 'string' }
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
]
|
|
687
|
+
}
|
|
688
|
+
const stringify = build(schema)
|
|
689
|
+
t.equal(stringify({ prop1: 'test1' }), '{"prop1":"test1"}')
|
|
690
|
+
t.equal(stringify({ prop2: 'test2' }), '{"prop2":"test2"}')
|
|
691
|
+
t.equal(stringify({ prop1: 'test1', prop2: 'test2' }), '{"prop1":"test1","prop2":"test2"}')
|
|
692
|
+
})
|
|
693
|
+
|
|
694
|
+
test('recursive nested anyOfs', (t) => {
|
|
695
|
+
t.plan(1)
|
|
696
|
+
|
|
697
|
+
const schema = {
|
|
698
|
+
type: 'object',
|
|
699
|
+
properties: {
|
|
700
|
+
foo: {
|
|
701
|
+
additionalProperties: false,
|
|
702
|
+
anyOf: [{ $ref: '#' }]
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
const data = { foo: {} }
|
|
708
|
+
const stringify = build(schema)
|
|
709
|
+
t.equal(stringify(data), JSON.stringify(data))
|
|
710
|
+
})
|
|
711
|
+
|
|
712
|
+
test('recursive nested anyOfs', (t) => {
|
|
713
|
+
t.plan(1)
|
|
714
|
+
|
|
715
|
+
const schema = {
|
|
716
|
+
type: 'object',
|
|
717
|
+
properties: {
|
|
718
|
+
foo: {
|
|
719
|
+
additionalProperties: false,
|
|
720
|
+
anyOf: [{ anyOf: [{ $ref: '#' }] }]
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
const data = { foo: {} }
|
|
726
|
+
const stringify = build(schema)
|
|
727
|
+
t.equal(stringify(data), JSON.stringify(data))
|
|
728
|
+
})
|
|
729
|
+
|
|
730
|
+
test('external recursive anyOfs', (t) => {
|
|
731
|
+
t.plan(1)
|
|
732
|
+
|
|
733
|
+
const externalSchema = {
|
|
734
|
+
type: 'object',
|
|
735
|
+
properties: {
|
|
736
|
+
foo: {
|
|
737
|
+
properties: {
|
|
738
|
+
bar: { type: 'string' }
|
|
739
|
+
},
|
|
740
|
+
anyOf: [{ $ref: '#' }]
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
const schema = {
|
|
746
|
+
type: 'object',
|
|
747
|
+
properties: {
|
|
748
|
+
a: { $ref: 'externalSchema#/properties/foo' },
|
|
749
|
+
b: { $ref: 'externalSchema#/properties/foo' }
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
const data = {
|
|
754
|
+
a: {
|
|
755
|
+
foo: {},
|
|
756
|
+
bar: '42',
|
|
757
|
+
baz: 42
|
|
758
|
+
},
|
|
759
|
+
b: {
|
|
760
|
+
foo: {},
|
|
761
|
+
bar: '42',
|
|
762
|
+
baz: 42
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
const stringify = build(schema, { schema: { externalSchema } })
|
|
766
|
+
t.equal(stringify(data), '{"a":{"bar":"42","foo":{}},"b":{"bar":"42","foo":{}}}')
|
|
767
|
+
})
|
|
@@ -249,15 +249,7 @@ const alphabetOutput = JSON.stringify({
|
|
|
249
249
|
const deepFoobarOutput = JSON.stringify({
|
|
250
250
|
foobar: JSON.parse(foobarOutput)
|
|
251
251
|
})
|
|
252
|
-
const noElseGreetingOutput = JSON.stringify({
|
|
253
|
-
kind: 'greeting',
|
|
254
|
-
foo: 'FOO',
|
|
255
|
-
bar: 42,
|
|
256
|
-
hi: 'HI',
|
|
257
|
-
hello: 45,
|
|
258
|
-
a: 'A',
|
|
259
|
-
b: 35
|
|
260
|
-
})
|
|
252
|
+
const noElseGreetingOutput = JSON.stringify({})
|
|
261
253
|
|
|
262
254
|
t.test('if-then-else', t => {
|
|
263
255
|
const tests = [
|
|
@@ -419,3 +411,60 @@ t.test('if/else with array', (t) => {
|
|
|
419
411
|
t.equal(stringify(['1']), JSON.stringify(['1']))
|
|
420
412
|
t.equal(stringify(['1', '2']), JSON.stringify([1, 2]))
|
|
421
413
|
})
|
|
414
|
+
|
|
415
|
+
t.test('external recursive if/then/else', (t) => {
|
|
416
|
+
t.plan(1)
|
|
417
|
+
|
|
418
|
+
const externalSchema = {
|
|
419
|
+
type: 'object',
|
|
420
|
+
properties: {
|
|
421
|
+
base: { type: 'string' },
|
|
422
|
+
self: { $ref: 'externalSchema#' }
|
|
423
|
+
},
|
|
424
|
+
if: {
|
|
425
|
+
type: 'object',
|
|
426
|
+
properties: {
|
|
427
|
+
foo: { type: 'string', const: '41' }
|
|
428
|
+
}
|
|
429
|
+
},
|
|
430
|
+
then: {
|
|
431
|
+
type: 'object',
|
|
432
|
+
properties: {
|
|
433
|
+
bar: { type: 'string', const: '42' }
|
|
434
|
+
}
|
|
435
|
+
},
|
|
436
|
+
else: {
|
|
437
|
+
type: 'object',
|
|
438
|
+
properties: {
|
|
439
|
+
baz: { type: 'string', const: '43' }
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
const schema = {
|
|
445
|
+
type: 'object',
|
|
446
|
+
properties: {
|
|
447
|
+
a: { $ref: 'externalSchema#/properties/self' },
|
|
448
|
+
b: { $ref: 'externalSchema#/properties/self' }
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
const data = {
|
|
453
|
+
a: {
|
|
454
|
+
base: 'a',
|
|
455
|
+
foo: '41',
|
|
456
|
+
bar: '42',
|
|
457
|
+
baz: '43',
|
|
458
|
+
ignore: 'ignored'
|
|
459
|
+
},
|
|
460
|
+
b: {
|
|
461
|
+
base: 'b',
|
|
462
|
+
foo: 'not-41',
|
|
463
|
+
bar: '42',
|
|
464
|
+
baz: '43',
|
|
465
|
+
ignore: 'ignored'
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
const stringify = build(schema, { schema: { externalSchema } })
|
|
469
|
+
t.equal(stringify(data), '{"a":{"base":"a","bar":"42"},"b":{"base":"b","baz":"43"}}')
|
|
470
|
+
})
|
package/test/required.test.js
CHANGED
|
@@ -126,17 +126,18 @@ test('object with multiple required field not in properties schema', (t) => {
|
|
|
126
126
|
stringify({})
|
|
127
127
|
t.fail()
|
|
128
128
|
} catch (e) {
|
|
129
|
-
t.equal(e.message, '"
|
|
129
|
+
t.equal(e.message, '"key1" is required!')
|
|
130
130
|
t.pass()
|
|
131
131
|
}
|
|
132
132
|
|
|
133
133
|
try {
|
|
134
134
|
stringify({
|
|
135
|
-
|
|
135
|
+
key1: 42,
|
|
136
|
+
key2: 42
|
|
136
137
|
})
|
|
137
138
|
t.fail()
|
|
138
139
|
} catch (e) {
|
|
139
|
-
t.equal(e.message, '"
|
|
140
|
+
t.equal(e.message, '"num" is required!')
|
|
140
141
|
t.pass()
|
|
141
142
|
}
|
|
142
143
|
|
package/test/toJSON.test.js
CHANGED
|
@@ -127,7 +127,7 @@ test('not fail on null sub-object declared nullable', (t) => {
|
|
|
127
127
|
t.equal('{"product":null}', stringify(object))
|
|
128
128
|
})
|
|
129
129
|
|
|
130
|
-
test('
|
|
130
|
+
test('on non nullable null sub-object it should coerce to {}', (t) => {
|
|
131
131
|
t.plan(1)
|
|
132
132
|
|
|
133
133
|
const stringify = build({
|
|
@@ -148,10 +148,12 @@ test('throw an error on non nullable null sub-object', (t) => {
|
|
|
148
148
|
const object = {
|
|
149
149
|
product: null
|
|
150
150
|
}
|
|
151
|
-
|
|
151
|
+
|
|
152
|
+
const result = stringify(object)
|
|
153
|
+
t.equal(result, JSON.stringify({ product: {} }))
|
|
152
154
|
})
|
|
153
155
|
|
|
154
|
-
test('
|
|
156
|
+
test('on non nullable null object it should coerce to {}', (t) => {
|
|
155
157
|
t.plan(1)
|
|
156
158
|
|
|
157
159
|
const stringify = build({
|
|
@@ -170,5 +172,32 @@ test('throw an error on non nullable null object', (t) => {
|
|
|
170
172
|
}
|
|
171
173
|
}
|
|
172
174
|
})
|
|
173
|
-
|
|
175
|
+
|
|
176
|
+
const result = stringify(null)
|
|
177
|
+
t.equal(result, '{}')
|
|
178
|
+
})
|
|
179
|
+
|
|
180
|
+
test('on non-nullable null object it should skip rendering, skipping required fields checks', (t) => {
|
|
181
|
+
t.plan(1)
|
|
182
|
+
|
|
183
|
+
const stringify = build({
|
|
184
|
+
title: 'simple object',
|
|
185
|
+
nullable: false,
|
|
186
|
+
type: 'object',
|
|
187
|
+
properties: {
|
|
188
|
+
product: {
|
|
189
|
+
nullable: false,
|
|
190
|
+
type: 'object',
|
|
191
|
+
properties: {
|
|
192
|
+
name: {
|
|
193
|
+
type: 'string'
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
},
|
|
198
|
+
required: ['product']
|
|
199
|
+
})
|
|
200
|
+
|
|
201
|
+
const result = stringify(null)
|
|
202
|
+
t.equal(result, '{}')
|
|
174
203
|
})
|
package/test/typesArray.test.js
CHANGED
|
@@ -467,7 +467,7 @@ test('class instance that is simultaneously a string and a json', (t) => {
|
|
|
467
467
|
t.equal(valueObj, '{"simultaneously":{"foo":"hello"}}')
|
|
468
468
|
})
|
|
469
469
|
|
|
470
|
-
test('should throw an error when type is array and object is null', (t) => {
|
|
470
|
+
test('should not throw an error when type is array and object is null, it should instead coerce to []', (t) => {
|
|
471
471
|
t.plan(1)
|
|
472
472
|
const schema = {
|
|
473
473
|
type: 'object',
|
|
@@ -482,7 +482,8 @@ test('should throw an error when type is array and object is null', (t) => {
|
|
|
482
482
|
}
|
|
483
483
|
|
|
484
484
|
const stringify = build(schema)
|
|
485
|
-
|
|
485
|
+
const result = stringify({ arr: null })
|
|
486
|
+
t.equal(result, JSON.stringify({ arr: [] }))
|
|
486
487
|
})
|
|
487
488
|
|
|
488
489
|
test('should throw an error when type is array and object is not an array', (t) => {
|