fast-json-stringify 5.11.0 → 5.11.1

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/index.js CHANGED
@@ -2,13 +2,14 @@
2
2
 
3
3
  /* eslint no-prototype-builtins: 0 */
4
4
 
5
+ const merge = require('@fastify/deepmerge')()
6
+ const clone = require('rfdc')({ proto: true })
5
7
  const { RefResolver } = require('json-schema-ref-resolver')
6
8
 
9
+ const validate = require('./lib/schema-validator')
7
10
  const Serializer = require('./lib/serializer')
8
11
  const Validator = require('./lib/validator')
9
12
  const Location = require('./lib/location')
10
- const validate = require('./lib/schema-validator')
11
- const mergeSchemas = require('./lib/merge-schemas')
12
13
 
13
14
  const SINGLE_TICK = /'/g
14
15
 
@@ -27,9 +28,9 @@ const validLargeArrayMechanisms = [
27
28
  'json-stringify'
28
29
  ]
29
30
 
30
- let schemaIdCounter = 0
31
+ const addComma = '!addComma && (addComma = true) || (json += \',\')'
31
32
 
32
- const mergedSchemaRef = Symbol('fjs-merged-schema-ref')
33
+ let schemaIdCounter = 0
33
34
 
34
35
  function isValidSchema (schema, name) {
35
36
  if (!validate(schema)) {
@@ -45,15 +46,13 @@ function isValidSchema (schema, name) {
45
46
  }
46
47
  }
47
48
 
48
- function resolveRef (context, location) {
49
- const ref = location.schema.$ref
50
-
49
+ function resolveRef (context, location, ref) {
51
50
  let hashIndex = ref.indexOf('#')
52
51
  if (hashIndex === -1) {
53
52
  hashIndex = ref.length
54
53
  }
55
54
 
56
- const schemaId = ref.slice(0, hashIndex) || location.schemaId
55
+ const schemaId = ref.slice(0, hashIndex) || location.getOriginSchemaId()
57
56
  const jsonPointer = ref.slice(hashIndex) || '#'
58
57
 
59
58
  const schema = context.refResolver.getSchema(schemaId, jsonPointer)
@@ -63,17 +62,12 @@ function resolveRef (context, location) {
63
62
 
64
63
  const newLocation = new Location(schema, schemaId, jsonPointer)
65
64
  if (schema.$ref !== undefined) {
66
- return resolveRef(context, newLocation)
65
+ return resolveRef(context, newLocation, schema.$ref)
67
66
  }
68
67
 
69
68
  return newLocation
70
69
  }
71
70
 
72
- function getMergedLocation (context, mergedSchemaId) {
73
- const mergedSchema = context.refResolver.getSchema(mergedSchemaId, '#')
74
- return new Location(mergedSchema, mergedSchemaId, '#')
75
- }
76
-
77
71
  function getSchemaId (schema, rootSchemaId) {
78
72
  if (schema.$id && schema.$id.charAt(0) !== '#') {
79
73
  return schema.$id
@@ -91,6 +85,7 @@ function build (schema, options) {
91
85
  functionsCounter: 0,
92
86
  functionsNamesBySchema: new Map(),
93
87
  options,
88
+ wrapObjects: true,
94
89
  refResolver: new RefResolver(),
95
90
  rootSchemaId: schema.$id || `__fjs_root_${schemaIdCounter++}`,
96
91
  validatorSchemasIds: new Set()
@@ -260,7 +255,7 @@ function inferTypeByKeyword (schema) {
260
255
  return schema.type
261
256
  }
262
257
 
263
- function buildExtraObjectPropertiesSerializer (context, location, addComma) {
258
+ function buildExtraObjectPropertiesSerializer (context, location) {
264
259
  const schema = location.schema
265
260
  const propertiesKeys = Object.keys(schema.properties || {})
266
261
 
@@ -319,137 +314,225 @@ function buildExtraObjectPropertiesSerializer (context, location, addComma) {
319
314
  }
320
315
 
321
316
  function buildInnerObject (context, location) {
317
+ let code = ''
322
318
  const schema = location.schema
319
+ const required = schema.required || []
323
320
 
324
321
  const propertiesLocation = location.getPropertyLocation('properties')
325
- const requiredProperties = schema.required || []
326
322
 
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])
323
+ const requiredWithDefault = []
324
+ const requiredWithoutDefault = []
325
+ if (schema.properties) {
326
+ for (const key of Object.keys(schema.properties)) {
327
+ if (required.indexOf(key) === -1) {
328
+ continue
329
+ }
330
+ let propertyLocation = propertiesLocation.getPropertyLocation(key)
331
+ if (propertyLocation.schema.$ref) {
332
+ propertyLocation = resolveRef(context, location, propertyLocation.schema.$ref)
333
+ }
336
334
 
337
- let code = ''
335
+ const sanitizedKey = JSON.stringify(key)
338
336
 
339
- for (const key of requiredProperties) {
340
- if (!propertiesKeys.includes(key)) {
341
- code += `if (obj['${key}'] === undefined) throw new Error('"${key}" is required!')\n`
337
+ // Using obj['key'] !== undefined instead of obj.hasOwnProperty(prop) for perf reasons,
338
+ // see https://github.com/mcollina/fast-json-stringify/pull/3 for discussion.
339
+ const defaultValue = propertyLocation.schema.default
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)
342
345
  }
343
346
  }
344
347
 
345
- code += 'let json = \'{\'\n'
346
-
347
- let addComma = ''
348
- if (!hasRequiredProperties) {
349
- code += 'let addComma = false\n'
350
- addComma = '!addComma && (addComma = true) || (json += \',\')'
348
+ // handle extraneous required fields
349
+ for (const requiredProperty of required) {
350
+ if (requiredWithDefault.indexOf(requiredProperty) !== -1) continue
351
+ code += `if (obj['${requiredProperty}'] === undefined) throw new Error('"${requiredProperty}" is required!')\n`
351
352
  }
352
353
 
353
- for (const key of propertiesKeys) {
354
- let propertyLocation = propertiesLocation.getPropertyLocation(key)
355
- if (propertyLocation.schema.$ref) {
356
- propertyLocation = resolveRef(context, propertyLocation)
357
- }
354
+ code += `
355
+ let addComma = false
356
+ let json = '${context.wrapObjects ? '{' : ''}'
357
+ `
358
+ const wrapObjects = context.wrapObjects
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
+ }
358
367
 
359
- const sanitizedKey = JSON.stringify(key)
360
- const defaultValue = propertyLocation.schema.default
361
- const isRequired = requiredProperties.includes(key)
368
+ const sanitizedKey = JSON.stringify(key)
362
369
 
363
- code += `
364
- if (obj[${sanitizedKey}] !== undefined) {
370
+ if (requiredWithoutDefault.indexOf(key) !== -1) {
371
+ code += `
365
372
  ${addComma}
366
373
  json += ${JSON.stringify(sanitizedKey + ':')}
367
374
  ${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
- } else if (isRequired) {
377
- code += ` else {
378
- throw new Error('${sanitizedKey} is required!')
376
+ } else {
377
+ // Using obj['key'] !== undefined instead of obj.hasOwnProperty(prop) for perf reasons,
378
+ // see https://github.com/mcollina/fast-json-stringify/pull/3 for discussion.
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
+ }
379
395
  }
380
- `
381
- } else {
382
- code += '\n'
383
- }
384
-
385
- if (hasRequiredProperties) {
386
- addComma = 'json += \',\''
387
396
  }
388
397
  }
389
398
 
390
399
  if (schema.patternProperties || schema.additionalProperties) {
391
- code += buildExtraObjectPropertiesSerializer(context, location, addComma)
400
+ code += buildExtraObjectPropertiesSerializer(context, location)
392
401
  }
393
402
 
403
+ context.wrapObjects = wrapObjects
394
404
  code += `
395
- return json + '}'
405
+ return json${context.wrapObjects ? ' + \'}\'' : ''}
396
406
  `
397
407
  return code
398
408
  }
399
409
 
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)
410
+ function mergeAllOfSchema (context, location, schema, mergedSchema) {
411
+ const allOfLocation = location.getPropertyLocation('allOf')
412
+
413
+ for (let i = 0; i < schema.allOf.length; i++) {
414
+ let allOfSchema = schema.allOf[i]
415
+
416
+ if (allOfSchema.$ref) {
417
+ const allOfSchemaLocation = allOfLocation.getPropertyLocation(i)
418
+ allOfSchema = resolveRef(context, allOfSchemaLocation, allOfSchema.$ref).schema
406
419
  }
407
- }
408
420
 
409
- const mergedSchemas = []
410
- for (const location of mergedLocations) {
411
- const schema = cloneOriginSchema(location.schema, location.schemaId)
412
- delete schema.$id
421
+ let allOfSchemaType = allOfSchema.type
422
+ if (allOfSchemaType === undefined) {
423
+ allOfSchemaType = inferTypeByKeyword(allOfSchema)
424
+ }
413
425
 
414
- mergedSchemas.push(schema)
415
- }
426
+ if (allOfSchemaType !== undefined) {
427
+ if (
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
+ }
416
435
 
417
- const mergedSchema = mergeSchemas(mergedSchemas)
418
- const mergedLocation = new Location(mergedSchema, mergedSchemaId)
436
+ if (allOfSchema.format !== undefined) {
437
+ if (
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
+ }
419
445
 
420
- context.refResolver.addSchema(mergedSchema, mergedSchemaId)
421
- return mergedLocation
422
- }
446
+ if (allOfSchema.nullable !== undefined) {
447
+ if (
448
+ mergedSchema.nullable !== undefined &&
449
+ mergedSchema.nullable !== allOfSchema.nullable
450
+ ) {
451
+ throw new Error('allOf schemas have different nullable values')
452
+ }
453
+ mergedSchema.nullable = allOfSchema.nullable
454
+ }
423
455
 
424
- function cloneOriginSchema (schema, schemaId) {
425
- const clonedSchema = Array.isArray(schema) ? [] : {}
456
+ if (allOfSchema.properties !== undefined) {
457
+ if (mergedSchema.properties === undefined) {
458
+ mergedSchema.properties = {}
459
+ }
460
+ Object.assign(mergedSchema.properties, allOfSchema.properties)
461
+ }
426
462
 
427
- if (
428
- schema.$id !== undefined &&
429
- schema.$id.charAt(0) !== '#'
430
- ) {
431
- schemaId = schema.$id
432
- }
463
+ if (allOfSchema.additionalProperties !== undefined) {
464
+ if (mergedSchema.additionalProperties === undefined) {
465
+ mergedSchema.additionalProperties = {}
466
+ }
467
+ Object.assign(mergedSchema.additionalProperties, allOfSchema.additionalProperties)
468
+ }
433
469
 
434
- if (schema[mergedSchemaRef]) {
435
- clonedSchema[mergedSchemaRef] = schema[mergedSchemaRef]
436
- }
470
+ if (allOfSchema.patternProperties !== undefined) {
471
+ if (mergedSchema.patternProperties === undefined) {
472
+ mergedSchema.patternProperties = {}
473
+ }
474
+ Object.assign(mergedSchema.patternProperties, allOfSchema.patternProperties)
475
+ }
437
476
 
438
- for (const key in schema) {
439
- let value = schema[key]
477
+ if (allOfSchema.required !== undefined) {
478
+ if (mergedSchema.required === undefined) {
479
+ mergedSchema.required = []
480
+ }
481
+ mergedSchema.required.push(...allOfSchema.required)
482
+ }
440
483
 
441
- if (key === '$ref' && value.charAt(0) === '#') {
442
- value = schemaId + value
484
+ if (allOfSchema.oneOf !== undefined) {
485
+ if (mergedSchema.oneOf === undefined) {
486
+ mergedSchema.oneOf = []
487
+ }
488
+ mergedSchema.oneOf.push(...allOfSchema.oneOf)
443
489
  }
444
490
 
445
- if (typeof value === 'object' && value !== null) {
446
- value = cloneOriginSchema(value, schemaId)
491
+ if (allOfSchema.anyOf !== undefined) {
492
+ if (mergedSchema.anyOf === undefined) {
493
+ mergedSchema.anyOf = []
494
+ }
495
+ mergedSchema.anyOf.push(...allOfSchema.anyOf)
447
496
  }
448
497
 
449
- clonedSchema[key] = value
498
+ if (allOfSchema.allOf !== undefined) {
499
+ mergeAllOfSchema(context, location, allOfSchema, mergedSchema)
500
+ }
450
501
  }
502
+ delete mergedSchema.allOf
451
503
 
452
- return clonedSchema
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
+
516
+ delete schema.if
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
+ `
453
536
  }
454
537
 
455
538
  function toJSON (variableName) {
@@ -499,7 +582,7 @@ function buildArray (context, location) {
499
582
  itemsLocation.schema = itemsLocation.schema || {}
500
583
 
501
584
  if (itemsLocation.schema.$ref) {
502
- itemsLocation = resolveRef(context, itemsLocation)
585
+ itemsLocation = resolveRef(context, itemsLocation, itemsLocation.schema.$ref)
503
586
  }
504
587
 
505
588
  const itemsSchema = itemsLocation.schema
@@ -777,193 +860,81 @@ function buildConstSerializer (location, input) {
777
860
  return code
778
861
  }
779
862
 
780
- function buildAllOf (context, location, input) {
781
- const schema = location.schema
863
+ function buildValue (context, location, input) {
864
+ let schema = location.schema
782
865
 
783
- let mergedSchemaId = schema[mergedSchemaRef]
784
- if (mergedSchemaId) {
785
- const mergedLocation = getMergedLocation(context, mergedSchemaId)
786
- return buildValue(context, mergedLocation, input)
866
+ if (typeof schema === 'boolean') {
867
+ return `json += JSON.stringify(${input})`
787
868
  }
788
869
 
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))
870
+ if (schema.$ref) {
871
+ location = resolveRef(context, location, schema.$ref)
872
+ schema = location.schema
804
873
  }
805
874
 
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
- ])
875
+ if (schema.type === undefined) {
876
+ const inferredType = inferTypeByKeyword(schema)
877
+ if (inferredType) {
878
+ schema.type = inferredType
843
879
  }
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
- `
851
880
  }
852
881
 
853
- let schemaRef = location.getSchemaRef()
854
- if (schemaRef.startsWith(context.rootSchemaId)) {
855
- schemaRef = schemaRef.replace(context.rootSchemaId, '')
882
+ if (schema.if && schema.then) {
883
+ return addIfThenElse(context, location, input)
856
884
  }
857
885
 
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
- ])
886
+ if (schema.allOf) {
887
+ mergeAllOfSchema(context, location, schema, clone(schema))
888
+ schema = location.schema
897
889
  }
898
890
 
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
- }
891
+ const type = schema.type
908
892
 
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
893
+ let code = ''
917
894
 
918
- elseMergedLocation = mergeLocations(context, elseMergedSchemaId, [
919
- rootLocation,
920
- elseLocation
921
- ])
922
- }
895
+ if ((type === undefined || type === 'object') && (schema.anyOf || schema.oneOf)) {
896
+ context.validatorSchemasIds.add(location.getSchemaId())
923
897
 
924
- return `
925
- if (validator.validate("${ifSchemaRef}", ${input})) {
926
- ${buildValue(context, thenMergedLocation, input)}
927
- } else {
928
- ${buildValue(context, elseMergedLocation, input)}
898
+ if (schema.type === 'object') {
899
+ context.wrapObjects = false
900
+ const funcName = buildObject(context, location)
901
+ code += `
902
+ json += '{'
903
+ json += ${funcName}(${input})
904
+ json += ','
905
+ `
929
906
  }
930
- `
931
- }
932
907
 
933
- function buildValue (context, location, input) {
934
- let schema = location.schema
908
+ const type = schema.anyOf ? 'anyOf' : 'oneOf'
909
+ const anyOfLocation = location.getPropertyLocation(type)
935
910
 
936
- if (typeof schema === 'boolean') {
937
- return `json += JSON.stringify(${input})`
938
- }
939
-
940
- if (schema.$ref) {
941
- location = resolveRef(context, location)
942
- schema = location.schema
943
- }
944
-
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
- }
911
+ for (let index = 0; index < location.schema[type].length; index++) {
912
+ const optionLocation = anyOfLocation.getPropertyLocation(index)
913
+ const schemaRef = optionLocation.getSchemaRef()
914
+ const nestedResult = buildValue(context, optionLocation, input)
915
+ code += `
916
+ ${index === 0 ? 'if' : 'else if'}(validator.validate("${schemaRef}", ${input}))
917
+ ${nestedResult}
918
+ `
919
+ }
952
920
 
953
- if (schema.if && schema.then) {
954
- return buildIfThenElse(context, location, input)
955
- }
921
+ let schemaRef = location.getSchemaRef()
922
+ if (schemaRef.startsWith(context.rootSchemaId)) {
923
+ schemaRef = schemaRef.replace(context.rootSchemaId, '')
924
+ }
956
925
 
957
- if (schema.type === undefined) {
958
- const inferredType = inferTypeByKeyword(schema)
959
- if (inferredType) {
960
- schema.type = inferredType
926
+ code += `
927
+ else throw new TypeError(\`The value of '${schemaRef}' does not match schema definition.\`)
928
+ `
929
+ if (schema.type === 'object') {
930
+ code += `
931
+ json += '}'
932
+ `
933
+ context.wrapObjects = true
961
934
  }
935
+ return code
962
936
  }
963
937
 
964
- let code = ''
965
-
966
- const type = schema.type
967
938
  const nullable = schema.nullable === true
968
939
  if (nullable) {
969
940
  code += `
package/lib/location.js CHANGED
@@ -5,6 +5,7 @@ class Location {
5
5
  this.schema = schema
6
6
  this.schemaId = schemaId
7
7
  this.jsonPointer = jsonPointer
8
+ this.mergedSchemaId = null
8
9
  }
9
10
 
10
11
  getPropertyLocation (propertyName) {
@@ -13,11 +14,39 @@ class Location {
13
14
  this.schemaId,
14
15
  this.jsonPointer + '/' + propertyName
15
16
  )
17
+
18
+ if (this.mergedSchemaId !== null) {
19
+ propertyLocation.addMergedSchema(
20
+ this.schema[propertyName],
21
+ this.mergedSchemaId,
22
+ this.jsonPointer + '/' + propertyName
23
+ )
24
+ }
25
+
16
26
  return propertyLocation
17
27
  }
18
28
 
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
+
19
41
  getSchemaRef () {
20
- return this.schemaId + this.jsonPointer
42
+ const schemaId = this.getSchemaId()
43
+ return schemaId + this.jsonPointer
44
+ }
45
+
46
+ addMergedSchema (mergedSchema, schemaId, jsonPointer = '#') {
47
+ this.schema = mergedSchema
48
+ this.mergedSchemaId = schemaId
49
+ this.jsonPointer = jsonPointer
21
50
  }
22
51
  }
23
52
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fast-json-stringify",
3
- "version": "5.11.0",
3
+ "version": "5.11.1",
4
4
  "description": "Stringify your JSON at max speed",
5
5
  "main": "index.js",
6
6
  "type": "commonjs",
@@ -51,13 +51,13 @@
51
51
  "fast-json-stringify": "."
52
52
  },
53
53
  "dependencies": {
54
+ "@fastify/deepmerge": "^1.0.0",
54
55
  "ajv": "^8.10.0",
55
56
  "ajv-formats": "^2.1.1",
56
57
  "fast-deep-equal": "^3.1.3",
57
58
  "fast-uri": "^2.1.0",
58
59
  "rfdc": "^1.2.0",
59
- "json-schema-ref-resolver": "^1.0.1",
60
- "@fastify/merge-json-schemas": "^0.1.0"
60
+ "json-schema-ref-resolver": "^1.0.1"
61
61
  },
62
62
  "standard": {
63
63
  "ignore": [
@@ -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","id":1,"tag":"otherString"}')
124
+ }), '{"name":"string","tag":"otherString","id":1}')
125
125
  })
126
126
 
127
127
  test('object with allOf and one schema on the allOf', (t) => {
@@ -553,60 +553,8 @@ 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
-
608
556
  test('allOf: throw Error if types mismatch ', (t) => {
609
- t.plan(3)
557
+ t.plan(1)
610
558
 
611
559
  const schema = {
612
560
  allOf: [
@@ -614,18 +562,11 @@ test('allOf: throw Error if types mismatch ', (t) => {
614
562
  { type: 'number' }
615
563
  ]
616
564
  }
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
- }
565
+ t.throws(() => build(schema), new Error('allOf schemas have different type values'))
625
566
  })
626
567
 
627
568
  test('allOf: throw Error if format mismatch ', (t) => {
628
- t.plan(3)
569
+ t.plan(1)
629
570
 
630
571
  const schema = {
631
572
  allOf: [
@@ -633,87 +574,29 @@ test('allOf: throw Error if format mismatch ', (t) => {
633
574
  { format: 'time' }
634
575
  ]
635
576
  }
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
- }
577
+ t.throws(() => build(schema), new Error('allOf schemas have different format values'))
644
578
  })
645
579
 
646
- test('recursive nested allOfs', (t) => {
580
+ test('allOf: throw Error if nullable mismatch /1', (t) => {
647
581
  t.plan(1)
648
582
 
649
583
  const schema = {
650
- type: 'object',
651
- properties: {
652
- foo: {
653
- additionalProperties: false,
654
- allOf: [{ $ref: '#' }]
655
- }
656
- }
657
- }
658
-
659
- const data = { foo: {} }
660
- const stringify = build(schema)
661
- t.equal(stringify(data), JSON.stringify(data))
662
- })
663
-
664
- test('recursive nested allOfs', (t) => {
665
- t.plan(1)
666
-
667
- const schema = {
668
- type: 'object',
669
- properties: {
670
- foo: {
671
- additionalProperties: false,
672
- allOf: [{ allOf: [{ $ref: '#' }] }]
673
- }
674
- }
584
+ allOf: [
585
+ { nullable: true },
586
+ { nullable: false }
587
+ ]
675
588
  }
676
-
677
- const data = { foo: {} }
678
- const stringify = build(schema)
679
- t.equal(stringify(data), JSON.stringify(data))
589
+ t.throws(() => build(schema), new Error('allOf schemas have different nullable values'))
680
590
  })
681
591
 
682
- test('external recursive allOfs', (t) => {
592
+ test('allOf: throw Error if nullable mismatch /2', (t) => {
683
593
  t.plan(1)
684
594
 
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
595
  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
- }
596
+ allOf: [
597
+ { nullable: false },
598
+ { nullable: true }
599
+ ]
716
600
  }
717
- const stringify = build(schema, { schema: { externalSchema } })
718
- t.equal(stringify(data), '{"a":{"bar":"42","foo":{}},"b":{"bar":"42","foo":{}}}')
601
+ t.throws(() => build(schema), new Error('allOf schemas have different nullable values'))
719
602
  })
@@ -644,124 +644,3 @@ 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,7 +249,15 @@ const alphabetOutput = JSON.stringify({
249
249
  const deepFoobarOutput = JSON.stringify({
250
250
  foobar: JSON.parse(foobarOutput)
251
251
  })
252
- const noElseGreetingOutput = JSON.stringify({})
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
+ })
253
261
 
254
262
  t.test('if-then-else', t => {
255
263
  const tests = [
@@ -411,60 +419,3 @@ t.test('if/else with array', (t) => {
411
419
  t.equal(stringify(['1']), JSON.stringify(['1']))
412
420
  t.equal(stringify(['1', '2']), JSON.stringify([1, 2]))
413
421
  })
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
- })
@@ -126,18 +126,17 @@ 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, '"key1" is required!')
129
+ t.equal(e.message, '"num" is required!')
130
130
  t.pass()
131
131
  }
132
132
 
133
133
  try {
134
134
  stringify({
135
- key1: 42,
136
- key2: 42
135
+ num: 42
137
136
  })
138
137
  t.fail()
139
138
  } catch (e) {
140
- t.equal(e.message, '"num" is required!')
139
+ t.equal(e.message, '"key1" is required!')
141
140
  t.pass()
142
141
  }
143
142
 
@@ -1,9 +0,0 @@
1
- 'use strict'
2
-
3
- const { mergeSchemas: _mergeSchemas } = require('@fastify/merge-json-schemas')
4
-
5
- function mergeSchemas (schemas) {
6
- return _mergeSchemas(schemas, { onConflict: 'skip' })
7
- }
8
-
9
- module.exports = mergeSchemas