fast-json-stringify 4.1.0 → 5.0.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/README.md +2 -2
- package/ajv.js +1 -1
- package/index.d.ts +25 -1
- package/index.js +149 -287
- package/package.json +4 -8
- package/serializer.js +17 -22
- package/standalone.js +42 -0
- package/test/allof.test.js +9 -7
- package/test/ref.test.js +679 -37
- package/test/standalone-mode.test.js +88 -5
- package/test/types/test.ts +9 -1
package/index.js
CHANGED
|
@@ -12,7 +12,6 @@ const Serializer = require('./serializer')
|
|
|
12
12
|
const buildAjv = require('./ajv')
|
|
13
13
|
|
|
14
14
|
let largeArraySize = 2e4
|
|
15
|
-
let stringSimilarity = null
|
|
16
15
|
let largeArrayMechanism = 'default'
|
|
17
16
|
const validLargeArrayMechanisms = [
|
|
18
17
|
'default',
|
|
@@ -41,36 +40,83 @@ function isValidSchema (schema, name) {
|
|
|
41
40
|
}
|
|
42
41
|
}
|
|
43
42
|
|
|
44
|
-
function mergeLocation (
|
|
43
|
+
function mergeLocation (location, key) {
|
|
45
44
|
return {
|
|
46
|
-
schema:
|
|
47
|
-
|
|
48
|
-
|
|
45
|
+
schema: location.schema[key],
|
|
46
|
+
schemaId: location.schemaId,
|
|
47
|
+
jsonPointer: location.jsonPointer + '/' + key
|
|
49
48
|
}
|
|
50
49
|
}
|
|
51
50
|
|
|
51
|
+
function resolveRef (location, ref) {
|
|
52
|
+
let hashIndex = ref.indexOf('#')
|
|
53
|
+
if (hashIndex === -1) {
|
|
54
|
+
hashIndex = ref.length
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const schemaId = ref.slice(0, hashIndex) || location.schemaId
|
|
58
|
+
const jsonPointer = ref.slice(hashIndex)
|
|
59
|
+
|
|
60
|
+
const schemaRef = schemaId + jsonPointer
|
|
61
|
+
|
|
62
|
+
let ajvSchema
|
|
63
|
+
try {
|
|
64
|
+
ajvSchema = ajvInstance.getSchema(schemaRef)
|
|
65
|
+
} catch (error) {
|
|
66
|
+
throw new Error(`Cannot find reference "${ref}"`)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (ajvSchema === undefined) {
|
|
70
|
+
throw new Error(`Cannot find reference "${ref}"`)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const schema = ajvSchema.schema
|
|
74
|
+
if (schema.$ref !== undefined) {
|
|
75
|
+
return resolveRef({ schema, schemaId, jsonPointer }, schema.$ref)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return { schema, schemaId, jsonPointer }
|
|
79
|
+
}
|
|
80
|
+
|
|
52
81
|
const arrayItemsReferenceSerializersMap = new Map()
|
|
53
82
|
const objectReferenceSerializersMap = new Map()
|
|
54
|
-
const schemaReferenceMap = new Map()
|
|
55
83
|
|
|
84
|
+
let rootSchemaId = null
|
|
56
85
|
let ajvInstance = null
|
|
57
86
|
let contextFunctions = null
|
|
58
87
|
|
|
59
88
|
function build (schema, options) {
|
|
89
|
+
schema = clone(schema)
|
|
90
|
+
|
|
60
91
|
arrayItemsReferenceSerializersMap.clear()
|
|
61
92
|
objectReferenceSerializersMap.clear()
|
|
62
|
-
schemaReferenceMap.clear()
|
|
63
93
|
|
|
64
94
|
contextFunctions = []
|
|
65
95
|
options = options || {}
|
|
66
96
|
|
|
67
97
|
ajvInstance = buildAjv(options.ajv)
|
|
98
|
+
rootSchemaId = schema.$id || randomUUID()
|
|
68
99
|
|
|
69
100
|
isValidSchema(schema)
|
|
101
|
+
extendDateTimeType(schema)
|
|
102
|
+
ajvInstance.addSchema(schema, rootSchemaId)
|
|
103
|
+
|
|
70
104
|
if (options.schema) {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
105
|
+
const externalSchemas = clone(options.schema)
|
|
106
|
+
|
|
107
|
+
for (const key of Object.keys(externalSchemas)) {
|
|
108
|
+
const externalSchema = externalSchemas[key]
|
|
109
|
+
isValidSchema(externalSchema, key)
|
|
110
|
+
extendDateTimeType(externalSchema)
|
|
111
|
+
|
|
112
|
+
let schemaKey = externalSchema.$id || key
|
|
113
|
+
if (externalSchema.$id !== undefined && externalSchema.$id[0] === '#') {
|
|
114
|
+
schemaKey = key + externalSchema.$id // relative URI
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (ajvInstance.getSchema(schemaKey) === undefined) {
|
|
118
|
+
ajvInstance.addSchema(externalSchema, schemaKey)
|
|
119
|
+
}
|
|
74
120
|
}
|
|
75
121
|
}
|
|
76
122
|
|
|
@@ -98,22 +144,8 @@ function build (schema, options) {
|
|
|
98
144
|
|
|
99
145
|
const serializer = new Serializer(options)
|
|
100
146
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
root: schema,
|
|
104
|
-
externalSchema: options.schema
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
if (schema.$ref) {
|
|
108
|
-
location = refFinder(schema.$ref, location)
|
|
109
|
-
schema = location.schema
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
if (schema.type === undefined) {
|
|
113
|
-
schema.type = inferTypeByKeyword(schema)
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
const code = buildValue('main', 'input', location)
|
|
147
|
+
const location = { schema, schemaId: rootSchemaId, jsonPointer: '#' }
|
|
148
|
+
const code = buildValue(location, 'input')
|
|
117
149
|
|
|
118
150
|
const contextFunctionCode = `
|
|
119
151
|
function main (input) {
|
|
@@ -136,19 +168,9 @@ function build (schema, options) {
|
|
|
136
168
|
}
|
|
137
169
|
|
|
138
170
|
if (options.mode === 'standalone') {
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
const Serializer = require('fast-json-stringify/serializer')
|
|
143
|
-
const buildAjv = require('fast-json-stringify/ajv')
|
|
144
|
-
|
|
145
|
-
const serializer = new Serializer(${JSON.stringify(options || {})})
|
|
146
|
-
const ajv = buildAjv(${JSON.stringify(options.ajv || {})})
|
|
147
|
-
|
|
148
|
-
${contextFunctionCode.replace('return main', '')}
|
|
149
|
-
|
|
150
|
-
module.exports = main
|
|
151
|
-
`
|
|
171
|
+
// lazy load
|
|
172
|
+
const buildStandaloneCode = require('./standalone')
|
|
173
|
+
return buildStandaloneCode(options, ajvInstance, contextFunctionCode)
|
|
152
174
|
}
|
|
153
175
|
|
|
154
176
|
/* eslint no-new-func: "off" */
|
|
@@ -156,10 +178,10 @@ module.exports = main
|
|
|
156
178
|
const stringifyFunc = contextFunc(ajvInstance, serializer)
|
|
157
179
|
|
|
158
180
|
ajvInstance = null
|
|
181
|
+
rootSchemaId = null
|
|
159
182
|
contextFunctions = null
|
|
160
183
|
arrayItemsReferenceSerializersMap.clear()
|
|
161
184
|
objectReferenceSerializersMap.clear()
|
|
162
|
-
schemaReferenceMap.clear()
|
|
163
185
|
|
|
164
186
|
return stringifyFunc
|
|
165
187
|
}
|
|
@@ -240,10 +262,11 @@ function addPatternProperties (location) {
|
|
|
240
262
|
if (properties[keys[i]]) continue
|
|
241
263
|
`
|
|
242
264
|
|
|
243
|
-
|
|
244
|
-
|
|
265
|
+
const patternPropertiesLocation = mergeLocation(location, 'patternProperties')
|
|
266
|
+
Object.keys(pp).forEach((regex) => {
|
|
267
|
+
let ppLocation = mergeLocation(patternPropertiesLocation, regex)
|
|
245
268
|
if (pp[regex].$ref) {
|
|
246
|
-
ppLocation =
|
|
269
|
+
ppLocation = resolveRef(ppLocation, pp[regex].$ref)
|
|
247
270
|
pp[regex] = ppLocation.schema
|
|
248
271
|
}
|
|
249
272
|
|
|
@@ -253,7 +276,7 @@ function addPatternProperties (location) {
|
|
|
253
276
|
throw new Error(`${err.message}. Found at ${regex} matching ${JSON.stringify(pp[regex])}`)
|
|
254
277
|
}
|
|
255
278
|
|
|
256
|
-
const valueCode = buildValue(
|
|
279
|
+
const valueCode = buildValue(ppLocation, 'obj[keys[i]]')
|
|
257
280
|
code += `
|
|
258
281
|
if (/${regex.replace(/\\*\//g, '\\/')}/.test(keys[i])) {
|
|
259
282
|
${addComma}
|
|
@@ -286,13 +309,13 @@ function additionalProperty (location) {
|
|
|
286
309
|
|
|
287
310
|
return code
|
|
288
311
|
}
|
|
289
|
-
let apLocation = mergeLocation(location,
|
|
312
|
+
let apLocation = mergeLocation(location, 'additionalProperties')
|
|
290
313
|
if (ap.$ref) {
|
|
291
|
-
apLocation =
|
|
314
|
+
apLocation = resolveRef(location, ap.$ref)
|
|
292
315
|
ap = apLocation.schema
|
|
293
316
|
}
|
|
294
317
|
|
|
295
|
-
const valueCode = buildValue(
|
|
318
|
+
const valueCode = buildValue(apLocation, 'obj[keys[i]]')
|
|
296
319
|
|
|
297
320
|
code += `
|
|
298
321
|
${addComma}
|
|
@@ -314,140 +337,9 @@ function addAdditionalProperties (location) {
|
|
|
314
337
|
`
|
|
315
338
|
}
|
|
316
339
|
|
|
317
|
-
function
|
|
318
|
-
let objSchema
|
|
319
|
-
const explore = (schema, searchedId) => {
|
|
320
|
-
Object.keys(schema || {}).forEach((key, i, a) => {
|
|
321
|
-
if (key === '$id' && schema[key] === searchedId) {
|
|
322
|
-
objSchema = schema
|
|
323
|
-
} else if (objSchema === undefined && typeof schema[key] === 'object') {
|
|
324
|
-
explore(schema[key], searchedId)
|
|
325
|
-
}
|
|
326
|
-
})
|
|
327
|
-
}
|
|
328
|
-
explore(schema, searchedId)
|
|
329
|
-
return objSchema
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
function refFinder (ref, location) {
|
|
333
|
-
const externalSchema = location.externalSchema
|
|
334
|
-
let root = location.root
|
|
335
|
-
let schema = location.schema
|
|
336
|
-
|
|
337
|
-
if (externalSchema && externalSchema[ref]) {
|
|
338
|
-
return {
|
|
339
|
-
schema: externalSchema[ref],
|
|
340
|
-
root: externalSchema[ref],
|
|
341
|
-
externalSchema
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
// Split file from walk
|
|
346
|
-
ref = ref.split('#')
|
|
347
|
-
|
|
348
|
-
// Check schemaReferenceMap for $id entry
|
|
349
|
-
if (ref[0] && schemaReferenceMap.has(ref[0])) {
|
|
350
|
-
schema = schemaReferenceMap.get(ref[0])
|
|
351
|
-
root = schemaReferenceMap.get(ref[0])
|
|
352
|
-
if (schema.$ref) {
|
|
353
|
-
return refFinder(schema.$ref, {
|
|
354
|
-
schema,
|
|
355
|
-
root,
|
|
356
|
-
externalSchema
|
|
357
|
-
})
|
|
358
|
-
}
|
|
359
|
-
} else if (ref[0]) { // If external file
|
|
360
|
-
schema = externalSchema[ref[0]]
|
|
361
|
-
root = externalSchema[ref[0]]
|
|
362
|
-
|
|
363
|
-
if (schema === undefined) {
|
|
364
|
-
findBadKey(externalSchema, [ref[0]])
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
if (schema.$ref) {
|
|
368
|
-
return refFinder(schema.$ref, {
|
|
369
|
-
schema,
|
|
370
|
-
root,
|
|
371
|
-
externalSchema
|
|
372
|
-
})
|
|
373
|
-
}
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
let code = 'return schema'
|
|
377
|
-
// If it has a path
|
|
378
|
-
if (ref[1]) {
|
|
379
|
-
// ref[1] could contain a JSON pointer - ex: /definitions/num
|
|
380
|
-
// or plain name fragment id without suffix # - ex: customId
|
|
381
|
-
const walk = ref[1].split('/')
|
|
382
|
-
if (walk.length === 1) {
|
|
383
|
-
const targetId = `#${ref[1]}`
|
|
384
|
-
let dereferenced = idFinder(schema, targetId)
|
|
385
|
-
if (dereferenced === undefined && !ref[0]) {
|
|
386
|
-
// eslint-disable-next-line
|
|
387
|
-
for (var key of Object.keys(externalSchema)) {
|
|
388
|
-
dereferenced = idFinder(externalSchema[key], targetId)
|
|
389
|
-
if (dereferenced !== undefined) {
|
|
390
|
-
root = externalSchema[key]
|
|
391
|
-
break
|
|
392
|
-
}
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
return {
|
|
397
|
-
schema: dereferenced,
|
|
398
|
-
root,
|
|
399
|
-
externalSchema
|
|
400
|
-
}
|
|
401
|
-
} else {
|
|
402
|
-
// eslint-disable-next-line
|
|
403
|
-
for (var i = 1; i < walk.length; i++) {
|
|
404
|
-
code += `[${JSON.stringify(walk[i])}]`
|
|
405
|
-
}
|
|
406
|
-
}
|
|
407
|
-
}
|
|
408
|
-
let result
|
|
409
|
-
try {
|
|
410
|
-
result = (new Function('schema', code))(root)
|
|
411
|
-
} catch (err) {}
|
|
412
|
-
|
|
413
|
-
if (result === undefined && ref[1]) {
|
|
414
|
-
const walk = ref[1].split('/')
|
|
415
|
-
findBadKey(schema, walk.slice(1))
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
if (result.$ref) {
|
|
419
|
-
return refFinder(result.$ref, {
|
|
420
|
-
schema,
|
|
421
|
-
root,
|
|
422
|
-
externalSchema
|
|
423
|
-
})
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
return {
|
|
427
|
-
schema: result,
|
|
428
|
-
root,
|
|
429
|
-
externalSchema
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
function findBadKey (obj, keys) {
|
|
433
|
-
if (keys.length === 0) return null
|
|
434
|
-
const key = keys.shift()
|
|
435
|
-
if (obj[key] === undefined) {
|
|
436
|
-
stringSimilarity = stringSimilarity || require('string-similarity')
|
|
437
|
-
const { bestMatch } = stringSimilarity.findBestMatch(key, Object.keys(obj))
|
|
438
|
-
if (bestMatch.rating >= 0.5) {
|
|
439
|
-
throw new Error(`Cannot find reference ${JSON.stringify(key)}, did you mean ${JSON.stringify(bestMatch.target)}?`)
|
|
440
|
-
} else {
|
|
441
|
-
throw new Error(`Cannot find reference ${JSON.stringify(key)}`)
|
|
442
|
-
}
|
|
443
|
-
}
|
|
444
|
-
return findBadKey(obj[key], keys)
|
|
445
|
-
}
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
function buildCode (location, locationPath) {
|
|
340
|
+
function buildCode (location) {
|
|
449
341
|
if (location.schema.$ref) {
|
|
450
|
-
location =
|
|
342
|
+
location = resolveRef(location, location.schema.$ref)
|
|
451
343
|
}
|
|
452
344
|
|
|
453
345
|
const schema = location.schema
|
|
@@ -455,10 +347,11 @@ function buildCode (location, locationPath) {
|
|
|
455
347
|
|
|
456
348
|
let code = ''
|
|
457
349
|
|
|
350
|
+
const propertiesLocation = mergeLocation(location, 'properties')
|
|
458
351
|
Object.keys(schema.properties || {}).forEach((key) => {
|
|
459
|
-
let propertyLocation = mergeLocation(
|
|
352
|
+
let propertyLocation = mergeLocation(propertiesLocation, key)
|
|
460
353
|
if (schema.properties[key].$ref) {
|
|
461
|
-
propertyLocation =
|
|
354
|
+
propertyLocation = resolveRef(location, schema.properties[key].$ref)
|
|
462
355
|
schema.properties[key] = propertyLocation.schema
|
|
463
356
|
}
|
|
464
357
|
|
|
@@ -474,7 +367,7 @@ function buildCode (location, locationPath) {
|
|
|
474
367
|
json += ${asString} + ':'
|
|
475
368
|
`
|
|
476
369
|
|
|
477
|
-
code += buildValue(
|
|
370
|
+
code += buildValue(propertyLocation, `obj[${JSON.stringify(key)}]`)
|
|
478
371
|
|
|
479
372
|
const defaultValue = schema.properties[key].default
|
|
480
373
|
if (defaultValue !== undefined) {
|
|
@@ -504,9 +397,14 @@ function buildCode (location, locationPath) {
|
|
|
504
397
|
}
|
|
505
398
|
|
|
506
399
|
function mergeAllOfSchema (location, schema, mergedSchema) {
|
|
507
|
-
|
|
400
|
+
const allOfLocation = mergeLocation(location, 'allOf')
|
|
401
|
+
|
|
402
|
+
for (let i = 0; i < schema.allOf.length; i++) {
|
|
403
|
+
let allOfSchema = schema.allOf[i]
|
|
404
|
+
|
|
508
405
|
if (allOfSchema.$ref) {
|
|
509
|
-
|
|
406
|
+
const allOfSchemaLocation = mergeLocation(allOfLocation, i)
|
|
407
|
+
allOfSchema = resolveRef(allOfSchemaLocation, allOfSchema.$ref).schema
|
|
510
408
|
}
|
|
511
409
|
|
|
512
410
|
let allOfSchemaType = allOfSchema.type
|
|
@@ -593,9 +491,9 @@ function mergeAllOfSchema (location, schema, mergedSchema) {
|
|
|
593
491
|
delete mergedSchema.allOf
|
|
594
492
|
}
|
|
595
493
|
|
|
596
|
-
function buildInnerObject (location
|
|
494
|
+
function buildInnerObject (location) {
|
|
597
495
|
const schema = location.schema
|
|
598
|
-
let code = buildCode(location
|
|
496
|
+
let code = buildCode(location)
|
|
599
497
|
if (schema.patternProperties) {
|
|
600
498
|
code += addPatternProperties(location)
|
|
601
499
|
} else if (schema.additionalProperties && !schema.patternProperties) {
|
|
@@ -604,49 +502,44 @@ function buildInnerObject (location, locationPath) {
|
|
|
604
502
|
return code
|
|
605
503
|
}
|
|
606
504
|
|
|
607
|
-
function addIfThenElse (location
|
|
608
|
-
|
|
505
|
+
function addIfThenElse (location) {
|
|
506
|
+
const schema = merge({}, location.schema)
|
|
507
|
+
const thenSchema = schema.then
|
|
508
|
+
const elseSchema = schema.else || { additionalProperties: true }
|
|
609
509
|
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
const then = copy.then
|
|
614
|
-
const e = copy.else ? copy.else : { additionalProperties: true }
|
|
615
|
-
delete copy.if
|
|
616
|
-
delete copy.then
|
|
617
|
-
delete copy.else
|
|
618
|
-
let merged = merge(copy, then)
|
|
619
|
-
let mergedLocation = mergeLocation(location, { schema: merged })
|
|
620
|
-
|
|
621
|
-
const schemaKey = i.$id || randomUUID()
|
|
622
|
-
ajvInstance.addSchema(i, schemaKey)
|
|
510
|
+
delete schema.if
|
|
511
|
+
delete schema.then
|
|
512
|
+
delete schema.else
|
|
623
513
|
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
514
|
+
const ifLocation = mergeLocation(location, 'if')
|
|
515
|
+
const ifSchemaRef = ifLocation.schemaId + ifLocation.jsonPointer
|
|
516
|
+
|
|
517
|
+
let code = `
|
|
518
|
+
if (ajv.validate("${ifSchemaRef}", obj)) {
|
|
627
519
|
`
|
|
628
|
-
if (merged.if && merged.then) {
|
|
629
|
-
code += addIfThenElse(mergedLocation, locationPath + 'Then')
|
|
630
|
-
}
|
|
631
520
|
|
|
632
|
-
|
|
521
|
+
const thenLocation = mergeLocation(location, 'then')
|
|
522
|
+
thenLocation.schema = merge(schema, thenSchema)
|
|
633
523
|
|
|
524
|
+
if (thenSchema.if && thenSchema.then) {
|
|
525
|
+
code += addIfThenElse(thenLocation)
|
|
526
|
+
}
|
|
527
|
+
code += buildInnerObject(thenLocation)
|
|
634
528
|
code += `
|
|
635
529
|
}
|
|
636
530
|
`
|
|
637
|
-
|
|
638
|
-
|
|
531
|
+
|
|
532
|
+
const elseLocation = mergeLocation(location, 'else')
|
|
533
|
+
elseLocation.schema = merge(schema, elseSchema)
|
|
639
534
|
|
|
640
535
|
code += `
|
|
641
536
|
else {
|
|
642
537
|
`
|
|
643
538
|
|
|
644
|
-
if (
|
|
645
|
-
code += addIfThenElse(
|
|
539
|
+
if (elseSchema.if && elseSchema.then) {
|
|
540
|
+
code += addIfThenElse(elseLocation)
|
|
646
541
|
}
|
|
647
|
-
|
|
648
|
-
code += buildInnerObject(mergedLocation, locationPath + 'Else')
|
|
649
|
-
|
|
542
|
+
code += buildInnerObject(elseLocation)
|
|
650
543
|
code += `
|
|
651
544
|
}
|
|
652
545
|
`
|
|
@@ -660,11 +553,8 @@ function toJSON (variableName) {
|
|
|
660
553
|
`
|
|
661
554
|
}
|
|
662
555
|
|
|
663
|
-
function buildObject (location
|
|
556
|
+
function buildObject (location) {
|
|
664
557
|
const schema = location.schema
|
|
665
|
-
if (schema.$id !== undefined) {
|
|
666
|
-
schemaReferenceMap.set(schema.$id, schema)
|
|
667
|
-
}
|
|
668
558
|
|
|
669
559
|
if (objectReferenceSerializersMap.has(schema)) {
|
|
670
560
|
return objectReferenceSerializersMap.get(schema)
|
|
@@ -673,9 +563,10 @@ function buildObject (location, locationPath) {
|
|
|
673
563
|
const functionName = generateFuncName()
|
|
674
564
|
objectReferenceSerializersMap.set(schema, functionName)
|
|
675
565
|
|
|
566
|
+
const schemaId = location.schemaId === rootSchemaId ? '' : location.schemaId
|
|
676
567
|
let functionCode = `
|
|
677
568
|
function ${functionName} (input) {
|
|
678
|
-
// ${
|
|
569
|
+
// ${schemaId + location.jsonPointer}
|
|
679
570
|
`
|
|
680
571
|
if (schema.nullable) {
|
|
681
572
|
functionCode += `
|
|
@@ -693,12 +584,9 @@ function buildObject (location, locationPath) {
|
|
|
693
584
|
|
|
694
585
|
let rCode
|
|
695
586
|
if (schema.if && schema.then) {
|
|
696
|
-
|
|
697
|
-
var valid
|
|
698
|
-
`
|
|
699
|
-
rCode = addIfThenElse(location, locationPath)
|
|
587
|
+
rCode = addIfThenElse(location)
|
|
700
588
|
} else {
|
|
701
|
-
rCode = buildInnerObject(location
|
|
589
|
+
rCode = buildInnerObject(location)
|
|
702
590
|
}
|
|
703
591
|
|
|
704
592
|
// Removes the comma if is the last element of the string (in case there are not properties)
|
|
@@ -712,17 +600,16 @@ function buildObject (location, locationPath) {
|
|
|
712
600
|
return functionName
|
|
713
601
|
}
|
|
714
602
|
|
|
715
|
-
function buildArray (location
|
|
603
|
+
function buildArray (location) {
|
|
716
604
|
let schema = location.schema
|
|
717
|
-
if (schema.$id !== undefined) {
|
|
718
|
-
schemaReferenceMap.set(schema.$id, schema)
|
|
719
|
-
}
|
|
720
605
|
|
|
721
606
|
// default to any items type
|
|
722
607
|
if (!schema.items) {
|
|
723
608
|
schema.items = {}
|
|
724
609
|
}
|
|
725
610
|
|
|
611
|
+
let itemsLocation = mergeLocation(location, 'items')
|
|
612
|
+
|
|
726
613
|
if (schema.items.$ref) {
|
|
727
614
|
if (!schema[fjsCloned]) {
|
|
728
615
|
location.schema = clone(location.schema)
|
|
@@ -730,7 +617,8 @@ function buildArray (location, locationPath) {
|
|
|
730
617
|
schema[fjsCloned] = true
|
|
731
618
|
}
|
|
732
619
|
|
|
733
|
-
location =
|
|
620
|
+
location = resolveRef(location, schema.items.$ref)
|
|
621
|
+
itemsLocation = location
|
|
734
622
|
schema.items = location.schema
|
|
735
623
|
}
|
|
736
624
|
|
|
@@ -741,9 +629,10 @@ function buildArray (location, locationPath) {
|
|
|
741
629
|
const functionName = generateFuncName()
|
|
742
630
|
arrayItemsReferenceSerializersMap.set(schema.items, functionName)
|
|
743
631
|
|
|
632
|
+
const schemaId = location.schemaId === rootSchemaId ? '' : location.schemaId
|
|
744
633
|
let functionCode = `
|
|
745
634
|
function ${functionName} (obj) {
|
|
746
|
-
// ${
|
|
635
|
+
// ${schemaId + location.jsonPointer}
|
|
747
636
|
`
|
|
748
637
|
|
|
749
638
|
if (schema.nullable) {
|
|
@@ -781,11 +670,10 @@ function buildArray (location, locationPath) {
|
|
|
781
670
|
let jsonOutput = ''
|
|
782
671
|
`
|
|
783
672
|
|
|
784
|
-
const accessor = '[i]'
|
|
785
673
|
if (Array.isArray(schema.items)) {
|
|
786
674
|
for (let i = 0; i < schema.items.length; i++) {
|
|
787
675
|
const item = schema.items[i]
|
|
788
|
-
const tmpRes = buildValue(
|
|
676
|
+
const tmpRes = buildValue(mergeLocation(itemsLocation, i), `obj[${i}]`)
|
|
789
677
|
functionCode += `
|
|
790
678
|
if (${i} < arrayLength) {
|
|
791
679
|
if (${buildArrayTypeCondition(item.type, `[${i}]`)}) {
|
|
@@ -813,7 +701,7 @@ function buildArray (location, locationPath) {
|
|
|
813
701
|
}`
|
|
814
702
|
}
|
|
815
703
|
} else {
|
|
816
|
-
const code = buildValue(
|
|
704
|
+
const code = buildValue(itemsLocation, 'obj[i]')
|
|
817
705
|
functionCode += `
|
|
818
706
|
for (let i = 0; i < arrayLength; i++) {
|
|
819
707
|
let json = ''
|
|
@@ -870,40 +758,17 @@ function buildArrayTypeCondition (type, accessor) {
|
|
|
870
758
|
return condition
|
|
871
759
|
}
|
|
872
760
|
|
|
873
|
-
function dereferenceOfRefs (location, type) {
|
|
874
|
-
if (!location.schema[fjsCloned]) {
|
|
875
|
-
const schemaClone = clone(location.schema)
|
|
876
|
-
schemaClone[fjsCloned] = true
|
|
877
|
-
location.schema = schemaClone
|
|
878
|
-
}
|
|
879
|
-
|
|
880
|
-
const schema = location.schema
|
|
881
|
-
const locations = []
|
|
882
|
-
|
|
883
|
-
schema[type].forEach((s, index) => {
|
|
884
|
-
// follow the refs
|
|
885
|
-
let sLocation = mergeLocation(location, { schema: s })
|
|
886
|
-
while (s.$ref) {
|
|
887
|
-
sLocation = refFinder(s.$ref, sLocation)
|
|
888
|
-
schema[type][index] = sLocation.schema
|
|
889
|
-
s = schema[type][index]
|
|
890
|
-
}
|
|
891
|
-
locations[index] = sLocation
|
|
892
|
-
})
|
|
893
|
-
|
|
894
|
-
return locations
|
|
895
|
-
}
|
|
896
|
-
|
|
897
761
|
let genFuncNameCounter = 0
|
|
898
762
|
function generateFuncName () {
|
|
899
763
|
return 'anonymous' + genFuncNameCounter++
|
|
900
764
|
}
|
|
901
765
|
|
|
902
|
-
function buildValue (
|
|
766
|
+
function buildValue (location, input) {
|
|
903
767
|
let schema = location.schema
|
|
904
768
|
|
|
905
769
|
if (schema.$ref) {
|
|
906
|
-
|
|
770
|
+
location = resolveRef(location, schema.$ref)
|
|
771
|
+
schema = location.schema
|
|
907
772
|
}
|
|
908
773
|
|
|
909
774
|
if (schema.type === undefined) {
|
|
@@ -950,41 +815,32 @@ function buildValue (locationPath, input, location) {
|
|
|
950
815
|
code += `json += ${funcName}(${input})`
|
|
951
816
|
break
|
|
952
817
|
case 'object':
|
|
953
|
-
funcName = buildObject(location
|
|
818
|
+
funcName = buildObject(location)
|
|
954
819
|
code += `json += ${funcName}(${input})`
|
|
955
820
|
break
|
|
956
821
|
case 'array':
|
|
957
|
-
funcName = buildArray(location
|
|
822
|
+
funcName = buildArray(location)
|
|
958
823
|
code += `json += ${funcName}(${input})`
|
|
959
824
|
break
|
|
960
825
|
case undefined:
|
|
961
|
-
if (schema.
|
|
826
|
+
if (schema.fjs_date_type) {
|
|
827
|
+
funcName = getStringSerializer(schema.fjs_date_type, nullable)
|
|
828
|
+
code += `json += ${funcName}(${input})`
|
|
829
|
+
break
|
|
830
|
+
} else if (schema.anyOf || schema.oneOf) {
|
|
962
831
|
// beware: dereferenceOfRefs has side effects and changes schema.anyOf
|
|
963
|
-
const
|
|
964
|
-
|
|
965
|
-
const nestedResult = buildValue(locationPath + 'i' + index, input, location)
|
|
966
|
-
// Since we are only passing the relevant schema to ajv.validate, it needs to be full dereferenced
|
|
967
|
-
// otherwise any $ref pointing to an external schema would result in an error.
|
|
968
|
-
// Full dereference of the schema happens as side effect of two functions:
|
|
969
|
-
// 1. `dereferenceOfRefs` loops through the `schema.anyOf`` array and replaces any top level reference
|
|
970
|
-
// with the actual schema
|
|
971
|
-
// 2. `buildValue`, through `buildCode`, replaces any reference in object properties with the actual schema
|
|
972
|
-
// (see https://github.com/fastify/fast-json-stringify/blob/6da3b3e8ac24b1ca5578223adedb4083b7adf8db/index.js#L631)
|
|
973
|
-
|
|
974
|
-
// Ajv does not support js date format. In order to properly validate objects containing a date,
|
|
975
|
-
// it needs to replace all occurrences of the string date format with a custom keyword fjs_date_type.
|
|
976
|
-
// (see https://github.com/fastify/fast-json-stringify/pull/441)
|
|
977
|
-
const extendedSchema = clone(location.schema)
|
|
978
|
-
extendDateTimeType(extendedSchema)
|
|
979
|
-
|
|
980
|
-
const schemaKey = location.schema.$id || randomUUID()
|
|
981
|
-
ajvInstance.addSchema(extendedSchema, schemaKey)
|
|
832
|
+
const type = schema.anyOf ? 'anyOf' : 'oneOf'
|
|
833
|
+
const anyOfLocation = mergeLocation(location, type)
|
|
982
834
|
|
|
835
|
+
for (let index = 0; index < location.schema[type].length; index++) {
|
|
836
|
+
const optionLocation = mergeLocation(anyOfLocation, index)
|
|
837
|
+
const schemaRef = optionLocation.schemaId + optionLocation.jsonPointer
|
|
838
|
+
const nestedResult = buildValue(optionLocation, input)
|
|
983
839
|
code += `
|
|
984
|
-
${index === 0 ? 'if' : 'else if'}(ajv.validate("${
|
|
840
|
+
${index === 0 ? 'if' : 'else if'}(ajv.validate("${schemaRef}", ${input}))
|
|
985
841
|
${nestedResult}
|
|
986
842
|
`
|
|
987
|
-
}
|
|
843
|
+
}
|
|
988
844
|
|
|
989
845
|
code += `
|
|
990
846
|
else throw new Error(\`The value $\{JSON.stringify(${input})} does not match schema definition.\`)
|
|
@@ -1021,10 +877,11 @@ function buildValue (locationPath, input, location) {
|
|
|
1021
877
|
} else {`
|
|
1022
878
|
}
|
|
1023
879
|
|
|
880
|
+
const locationClone = clone(location)
|
|
1024
881
|
sortedTypes.forEach((type, index) => {
|
|
1025
882
|
const statement = index === 0 ? 'if' : 'else if'
|
|
1026
|
-
|
|
1027
|
-
const nestedResult = buildValue(
|
|
883
|
+
locationClone.schema.type = type
|
|
884
|
+
const nestedResult = buildValue(locationClone, input)
|
|
1028
885
|
switch (type) {
|
|
1029
886
|
case 'string': {
|
|
1030
887
|
code += `
|
|
@@ -1073,7 +930,12 @@ function buildValue (locationPath, input, location) {
|
|
|
1073
930
|
return code
|
|
1074
931
|
}
|
|
1075
932
|
|
|
933
|
+
// Ajv does not support js date format. In order to properly validate objects containing a date,
|
|
934
|
+
// it needs to replace all occurrences of the string date format with a custom keyword fjs_date_type.
|
|
935
|
+
// (see https://github.com/fastify/fast-json-stringify/pull/441)
|
|
1076
936
|
function extendDateTimeType (schema) {
|
|
937
|
+
if (schema === null) return
|
|
938
|
+
|
|
1077
939
|
if (schema.type === 'string' && ['date-time', 'date', 'time'].includes(schema.format)) {
|
|
1078
940
|
schema.fjs_date_type = schema.format
|
|
1079
941
|
delete schema.type
|