fast-json-stringify 5.2.0 → 5.4.0

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