fast-json-stringify 4.0.0 → 5.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +31 -5
- package/ajv.js +37 -0
- package/index.d.ts +25 -1
- package/index.js +260 -586
- package/package.json +4 -8
- package/serializer.js +163 -0
- package/standalone.js +42 -0
- package/test/allof.test.js +9 -7
- package/test/array.test.js +29 -0
- package/test/fixtures/.keep +0 -0
- package/test/ref.test.js +610 -37
- package/test/standalone-mode.test.js +121 -0
- package/test/types/test.ts +9 -1
package/index.js
CHANGED
|
@@ -2,18 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
/* eslint no-prototype-builtins: 0 */
|
|
4
4
|
|
|
5
|
-
const Ajv = require('ajv')
|
|
6
|
-
const fastUri = require('fast-uri')
|
|
7
|
-
const ajvFormats = require('ajv-formats')
|
|
8
5
|
const merge = require('deepmerge')
|
|
9
6
|
const clone = require('rfdc')({ proto: true })
|
|
10
7
|
const fjsCloned = Symbol('fast-json-stringify.cloned')
|
|
11
8
|
const { randomUUID } = require('crypto')
|
|
12
9
|
|
|
13
10
|
const validate = require('./schema-validator')
|
|
11
|
+
const Serializer = require('./serializer')
|
|
12
|
+
const buildAjv = require('./ajv')
|
|
14
13
|
|
|
15
14
|
let largeArraySize = 2e4
|
|
16
|
-
let stringSimilarity = null
|
|
17
15
|
let largeArrayMechanism = 'default'
|
|
18
16
|
const validLargeArrayMechanisms = [
|
|
19
17
|
'default',
|
|
@@ -42,225 +40,83 @@ function isValidSchema (schema, name) {
|
|
|
42
40
|
}
|
|
43
41
|
}
|
|
44
42
|
|
|
45
|
-
function mergeLocation (
|
|
43
|
+
function mergeLocation (location, key) {
|
|
46
44
|
return {
|
|
47
|
-
schema:
|
|
48
|
-
|
|
49
|
-
|
|
45
|
+
schema: location.schema[key],
|
|
46
|
+
schemaId: location.schemaId,
|
|
47
|
+
jsonPointer: location.jsonPointer + '/' + key
|
|
50
48
|
}
|
|
51
49
|
}
|
|
52
50
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
let ajvInstance = null
|
|
58
|
-
|
|
59
|
-
class Serializer {
|
|
60
|
-
constructor (options = {}) {
|
|
61
|
-
switch (options.rounding) {
|
|
62
|
-
case 'floor':
|
|
63
|
-
this.parseInteger = Math.floor
|
|
64
|
-
break
|
|
65
|
-
case 'ceil':
|
|
66
|
-
this.parseInteger = Math.ceil
|
|
67
|
-
break
|
|
68
|
-
case 'round':
|
|
69
|
-
this.parseInteger = Math.round
|
|
70
|
-
break
|
|
71
|
-
default:
|
|
72
|
-
this.parseInteger = Math.trunc
|
|
73
|
-
break
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
asAny (i) {
|
|
78
|
-
return JSON.stringify(i)
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
asNull () {
|
|
82
|
-
return 'null'
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
asInteger (i) {
|
|
86
|
-
if (typeof i === 'bigint') {
|
|
87
|
-
return i.toString()
|
|
88
|
-
} else if (Number.isInteger(i)) {
|
|
89
|
-
return '' + i
|
|
90
|
-
} else {
|
|
91
|
-
/* eslint no-undef: "off" */
|
|
92
|
-
const integer = this.parseInteger(i)
|
|
93
|
-
if (Number.isNaN(integer)) {
|
|
94
|
-
throw new Error(`The value "${i}" cannot be converted to an integer.`)
|
|
95
|
-
} else {
|
|
96
|
-
return '' + integer
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
asIntegerNullable (i) {
|
|
102
|
-
return i === null ? 'null' : this.asInteger(i)
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
asNumber (i) {
|
|
106
|
-
const num = Number(i)
|
|
107
|
-
if (Number.isNaN(num)) {
|
|
108
|
-
throw new Error(`The value "${i}" cannot be converted to a number.`)
|
|
109
|
-
} else {
|
|
110
|
-
return '' + num
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
asNumberNullable (i) {
|
|
115
|
-
return i === null ? 'null' : this.asNumber(i)
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
asBoolean (bool) {
|
|
119
|
-
return bool && 'true' || 'false' // eslint-disable-line
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
asBooleanNullable (bool) {
|
|
123
|
-
return bool === null ? 'null' : this.asBoolean(bool)
|
|
51
|
+
function resolveRef (location, ref) {
|
|
52
|
+
let hashIndex = ref.indexOf('#')
|
|
53
|
+
if (hashIndex === -1) {
|
|
54
|
+
hashIndex = ref.length
|
|
124
55
|
}
|
|
125
56
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
if (date instanceof Date) {
|
|
129
|
-
return quotes + date.toISOString() + quotes
|
|
130
|
-
}
|
|
131
|
-
return this.asString(date, skipQuotes)
|
|
132
|
-
}
|
|
57
|
+
const schemaId = ref.slice(0, hashIndex) || location.schemaId
|
|
58
|
+
const jsonPointer = ref.slice(hashIndex)
|
|
133
59
|
|
|
134
|
-
|
|
135
|
-
return date === null ? 'null' : this.asDatetime(date, skipQuotes)
|
|
136
|
-
}
|
|
60
|
+
const schemaRef = schemaId + jsonPointer
|
|
137
61
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
}
|
|
143
|
-
return this.asString(date, skipQuotes)
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
asDateNullable (date, skipQuotes) {
|
|
147
|
-
return date === null ? 'null' : this.asDate(date, skipQuotes)
|
|
62
|
+
let ajvSchema
|
|
63
|
+
try {
|
|
64
|
+
ajvSchema = ajvInstance.getSchema(schemaRef)
|
|
65
|
+
} catch (error) {
|
|
66
|
+
throw new Error(`Cannot find reference "${ref}"`)
|
|
148
67
|
}
|
|
149
68
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
if (date instanceof Date) {
|
|
153
|
-
return quotes + new Date(date.getTime() - (date.getTimezoneOffset() * 60000)).toISOString().slice(11, 19) + quotes
|
|
154
|
-
}
|
|
155
|
-
return this.asString(date, skipQuotes)
|
|
69
|
+
if (ajvSchema === undefined) {
|
|
70
|
+
throw new Error(`Cannot find reference "${ref}"`)
|
|
156
71
|
}
|
|
157
72
|
|
|
158
|
-
|
|
159
|
-
|
|
73
|
+
const schema = ajvSchema.schema
|
|
74
|
+
if (schema.$ref !== undefined) {
|
|
75
|
+
return resolveRef({ schema, schemaId, jsonPointer }, schema.$ref)
|
|
160
76
|
}
|
|
161
77
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
if (str instanceof Date) {
|
|
165
|
-
return quotes + str.toISOString() + quotes
|
|
166
|
-
} else if (str === null) {
|
|
167
|
-
return quotes + quotes
|
|
168
|
-
} else if (str instanceof RegExp) {
|
|
169
|
-
str = str.source
|
|
170
|
-
} else if (typeof str !== 'string') {
|
|
171
|
-
str = str.toString()
|
|
172
|
-
}
|
|
173
|
-
// If we skipQuotes it means that we are using it as test
|
|
174
|
-
// no need to test the string length for the render
|
|
175
|
-
if (skipQuotes) {
|
|
176
|
-
return str
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
if (str.length < 42) {
|
|
180
|
-
return this.asStringSmall(str)
|
|
181
|
-
} else {
|
|
182
|
-
return JSON.stringify(str)
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
asStringNullable (str) {
|
|
187
|
-
return str === null ? 'null' : this.asString(str)
|
|
188
|
-
}
|
|
78
|
+
return { schema, schemaId, jsonPointer }
|
|
79
|
+
}
|
|
189
80
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
// everything below 32 needs JSON.stringify()
|
|
193
|
-
// every string that contain surrogate needs JSON.stringify()
|
|
194
|
-
// 34 and 92 happens all the time, so we
|
|
195
|
-
// have a fast case for them
|
|
196
|
-
asStringSmall (str) {
|
|
197
|
-
const l = str.length
|
|
198
|
-
let result = ''
|
|
199
|
-
let last = 0
|
|
200
|
-
let found = false
|
|
201
|
-
let surrogateFound = false
|
|
202
|
-
let point = 255
|
|
203
|
-
// eslint-disable-next-line
|
|
204
|
-
for (var i = 0; i < l && point >= 32; i++) {
|
|
205
|
-
point = str.charCodeAt(i)
|
|
206
|
-
if (point >= 0xD800 && point <= 0xDFFF) {
|
|
207
|
-
// The current character is a surrogate.
|
|
208
|
-
surrogateFound = true
|
|
209
|
-
}
|
|
210
|
-
if (point === 34 || point === 92) {
|
|
211
|
-
result += str.slice(last, i) + '\\'
|
|
212
|
-
last = i
|
|
213
|
-
found = true
|
|
214
|
-
}
|
|
215
|
-
}
|
|
81
|
+
const arrayItemsReferenceSerializersMap = new Map()
|
|
82
|
+
const objectReferenceSerializersMap = new Map()
|
|
216
83
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
result += str.slice(last)
|
|
221
|
-
}
|
|
222
|
-
return ((point < 32) || (surrogateFound === true)) ? JSON.stringify(str) : '"' + result + '"'
|
|
223
|
-
}
|
|
224
|
-
}
|
|
84
|
+
let rootSchemaId = null
|
|
85
|
+
let ajvInstance = null
|
|
86
|
+
let contextFunctions = null
|
|
225
87
|
|
|
226
88
|
function build (schema, options) {
|
|
89
|
+
schema = clone(schema)
|
|
90
|
+
|
|
227
91
|
arrayItemsReferenceSerializersMap.clear()
|
|
228
92
|
objectReferenceSerializersMap.clear()
|
|
229
|
-
schemaReferenceMap.clear()
|
|
230
93
|
|
|
94
|
+
contextFunctions = []
|
|
231
95
|
options = options || {}
|
|
232
96
|
|
|
233
|
-
ajvInstance =
|
|
234
|
-
|
|
97
|
+
ajvInstance = buildAjv(options.ajv)
|
|
98
|
+
rootSchemaId = schema.$id || randomUUID()
|
|
235
99
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
100
|
+
isValidSchema(schema)
|
|
101
|
+
extendDateTimeType(schema)
|
|
102
|
+
ajvInstance.addSchema(schema, rootSchemaId)
|
|
239
103
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
if (
|
|
250
|
-
|
|
251
|
-
}
|
|
252
|
-
if (schema === 'time') {
|
|
253
|
-
return validateTimeFormat(date)
|
|
104
|
+
if (options.schema) {
|
|
105
|
+
options.schema = clone(options.schema)
|
|
106
|
+
|
|
107
|
+
for (const key of Object.keys(options.schema)) {
|
|
108
|
+
const externalSchema = options.schema[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
|
|
254
115
|
}
|
|
255
|
-
return false
|
|
256
|
-
}
|
|
257
|
-
})
|
|
258
116
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
for (var key of Object.keys(options.schema)) {
|
|
263
|
-
isValidSchema(options.schema[key], key)
|
|
117
|
+
if (ajvInstance.getSchema(schemaKey) === undefined) {
|
|
118
|
+
ajvInstance.addSchema(externalSchema, schemaKey)
|
|
119
|
+
}
|
|
264
120
|
}
|
|
265
121
|
}
|
|
266
122
|
|
|
@@ -288,47 +144,44 @@ function build (schema, options) {
|
|
|
288
144
|
|
|
289
145
|
const serializer = new Serializer(options)
|
|
290
146
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
root: schema,
|
|
294
|
-
externalSchema: options.schema
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
if (schema.$ref) {
|
|
298
|
-
location = refFinder(schema.$ref, location)
|
|
299
|
-
schema = location.schema
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
if (schema.type === undefined) {
|
|
303
|
-
schema.type = inferTypeByKeyword(schema)
|
|
304
|
-
}
|
|
147
|
+
const location = { schema, schemaId: rootSchemaId, jsonPointer: '#' }
|
|
148
|
+
const code = buildValue(location, 'input')
|
|
305
149
|
|
|
306
|
-
const { code, laterCode } = buildValue('', 'main', 'input', location)
|
|
307
150
|
const contextFunctionCode = `
|
|
308
|
-
'use strict'
|
|
309
151
|
function main (input) {
|
|
310
152
|
let json = ''
|
|
311
153
|
${code}
|
|
312
154
|
return json
|
|
313
155
|
}
|
|
314
|
-
${
|
|
156
|
+
${contextFunctions.join('\n')}
|
|
315
157
|
return main
|
|
316
158
|
`
|
|
317
159
|
|
|
318
160
|
const dependenciesName = ['ajv', 'serializer', contextFunctionCode]
|
|
319
161
|
|
|
320
162
|
if (options.debugMode) {
|
|
163
|
+
options.mode = 'debug'
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (options.mode === 'debug') {
|
|
321
167
|
return { code: dependenciesName.join('\n'), ajv: ajvInstance }
|
|
322
168
|
}
|
|
323
169
|
|
|
170
|
+
if (options.mode === 'standalone') {
|
|
171
|
+
// lazy load
|
|
172
|
+
const buildStandaloneCode = require('./standalone')
|
|
173
|
+
return buildStandaloneCode(options, ajvInstance, contextFunctionCode)
|
|
174
|
+
}
|
|
175
|
+
|
|
324
176
|
/* eslint no-new-func: "off" */
|
|
325
177
|
const contextFunc = new Function('ajv', 'serializer', contextFunctionCode)
|
|
326
178
|
const stringifyFunc = contextFunc(ajvInstance, serializer)
|
|
327
179
|
|
|
328
180
|
ajvInstance = null
|
|
181
|
+
rootSchemaId = null
|
|
182
|
+
contextFunctions = null
|
|
329
183
|
arrayItemsReferenceSerializersMap.clear()
|
|
330
184
|
objectReferenceSerializersMap.clear()
|
|
331
|
-
schemaReferenceMap.clear()
|
|
332
185
|
|
|
333
186
|
return stringifyFunc
|
|
334
187
|
}
|
|
@@ -408,12 +261,12 @@ function addPatternProperties (location) {
|
|
|
408
261
|
for (var i = 0; i < keys.length; i++) {
|
|
409
262
|
if (properties[keys[i]]) continue
|
|
410
263
|
`
|
|
411
|
-
let laterCode = ''
|
|
412
264
|
|
|
413
|
-
|
|
414
|
-
|
|
265
|
+
const patternPropertiesLocation = mergeLocation(location, 'patternProperties')
|
|
266
|
+
Object.keys(pp).forEach((regex) => {
|
|
267
|
+
let ppLocation = mergeLocation(patternPropertiesLocation, regex)
|
|
415
268
|
if (pp[regex].$ref) {
|
|
416
|
-
ppLocation =
|
|
269
|
+
ppLocation = resolveRef(ppLocation, pp[regex].$ref)
|
|
417
270
|
pp[regex] = ppLocation.schema
|
|
418
271
|
}
|
|
419
272
|
|
|
@@ -423,27 +276,24 @@ function addPatternProperties (location) {
|
|
|
423
276
|
throw new Error(`${err.message}. Found at ${regex} matching ${JSON.stringify(pp[regex])}`)
|
|
424
277
|
}
|
|
425
278
|
|
|
426
|
-
const valueCode = buildValue(
|
|
427
|
-
laterCode += valueCode.laterCode
|
|
279
|
+
const valueCode = buildValue(ppLocation, 'obj[keys[i]]')
|
|
428
280
|
code += `
|
|
429
281
|
if (/${regex.replace(/\\*\//g, '\\/')}/.test(keys[i])) {
|
|
430
282
|
${addComma}
|
|
431
283
|
json += serializer.asString(keys[i]) + ':'
|
|
432
|
-
${valueCode
|
|
284
|
+
${valueCode}
|
|
433
285
|
continue
|
|
434
286
|
}
|
|
435
287
|
`
|
|
436
288
|
})
|
|
437
289
|
if (schema.additionalProperties) {
|
|
438
|
-
|
|
439
|
-
code += additionalPropertyCode.code
|
|
440
|
-
laterCode += additionalPropertyCode.laterCode
|
|
290
|
+
code += additionalProperty(location)
|
|
441
291
|
}
|
|
442
292
|
|
|
443
293
|
code += `
|
|
444
294
|
}
|
|
445
295
|
`
|
|
446
|
-
return
|
|
296
|
+
return code
|
|
447
297
|
}
|
|
448
298
|
|
|
449
299
|
function additionalProperty (location) {
|
|
@@ -457,182 +307,51 @@ function additionalProperty (location) {
|
|
|
457
307
|
}
|
|
458
308
|
`
|
|
459
309
|
|
|
460
|
-
return
|
|
310
|
+
return code
|
|
461
311
|
}
|
|
462
|
-
let apLocation = mergeLocation(location,
|
|
312
|
+
let apLocation = mergeLocation(location, 'additionalProperties')
|
|
463
313
|
if (ap.$ref) {
|
|
464
|
-
apLocation =
|
|
314
|
+
apLocation = resolveRef(location, ap.$ref)
|
|
465
315
|
ap = apLocation.schema
|
|
466
316
|
}
|
|
467
317
|
|
|
468
|
-
const valueCode = buildValue(
|
|
318
|
+
const valueCode = buildValue(apLocation, 'obj[keys[i]]')
|
|
469
319
|
|
|
470
320
|
code += `
|
|
471
321
|
${addComma}
|
|
472
322
|
json += serializer.asString(keys[i]) + ':'
|
|
473
|
-
${valueCode
|
|
323
|
+
${valueCode}
|
|
474
324
|
`
|
|
475
325
|
|
|
476
|
-
return
|
|
326
|
+
return code
|
|
477
327
|
}
|
|
478
328
|
|
|
479
329
|
function addAdditionalProperties (location) {
|
|
480
|
-
|
|
481
|
-
const code = `
|
|
330
|
+
return `
|
|
482
331
|
var properties = ${JSON.stringify(location.schema.properties)} || {}
|
|
483
332
|
var keys = Object.keys(obj)
|
|
484
333
|
for (var i = 0; i < keys.length; i++) {
|
|
485
334
|
if (properties[keys[i]]) continue
|
|
486
|
-
${
|
|
335
|
+
${additionalProperty(location)}
|
|
487
336
|
}
|
|
488
337
|
`
|
|
489
|
-
|
|
490
|
-
return { code, laterCode: additionalPropertyCode.laterCode }
|
|
491
|
-
}
|
|
492
|
-
|
|
493
|
-
function idFinder (schema, searchedId) {
|
|
494
|
-
let objSchema
|
|
495
|
-
const explore = (schema, searchedId) => {
|
|
496
|
-
Object.keys(schema || {}).forEach((key, i, a) => {
|
|
497
|
-
if (key === '$id' && schema[key] === searchedId) {
|
|
498
|
-
objSchema = schema
|
|
499
|
-
} else if (objSchema === undefined && typeof schema[key] === 'object') {
|
|
500
|
-
explore(schema[key], searchedId)
|
|
501
|
-
}
|
|
502
|
-
})
|
|
503
|
-
}
|
|
504
|
-
explore(schema, searchedId)
|
|
505
|
-
return objSchema
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
function refFinder (ref, location) {
|
|
509
|
-
const externalSchema = location.externalSchema
|
|
510
|
-
let root = location.root
|
|
511
|
-
let schema = location.schema
|
|
512
|
-
|
|
513
|
-
if (externalSchema && externalSchema[ref]) {
|
|
514
|
-
return {
|
|
515
|
-
schema: externalSchema[ref],
|
|
516
|
-
root: externalSchema[ref],
|
|
517
|
-
externalSchema
|
|
518
|
-
}
|
|
519
|
-
}
|
|
520
|
-
|
|
521
|
-
// Split file from walk
|
|
522
|
-
ref = ref.split('#')
|
|
523
|
-
|
|
524
|
-
// Check schemaReferenceMap for $id entry
|
|
525
|
-
if (ref[0] && schemaReferenceMap.has(ref[0])) {
|
|
526
|
-
schema = schemaReferenceMap.get(ref[0])
|
|
527
|
-
root = schemaReferenceMap.get(ref[0])
|
|
528
|
-
if (schema.$ref) {
|
|
529
|
-
return refFinder(schema.$ref, {
|
|
530
|
-
schema,
|
|
531
|
-
root,
|
|
532
|
-
externalSchema
|
|
533
|
-
})
|
|
534
|
-
}
|
|
535
|
-
} else if (ref[0]) { // If external file
|
|
536
|
-
schema = externalSchema[ref[0]]
|
|
537
|
-
root = externalSchema[ref[0]]
|
|
538
|
-
|
|
539
|
-
if (schema === undefined) {
|
|
540
|
-
findBadKey(externalSchema, [ref[0]])
|
|
541
|
-
}
|
|
542
|
-
|
|
543
|
-
if (schema.$ref) {
|
|
544
|
-
return refFinder(schema.$ref, {
|
|
545
|
-
schema,
|
|
546
|
-
root,
|
|
547
|
-
externalSchema
|
|
548
|
-
})
|
|
549
|
-
}
|
|
550
|
-
}
|
|
551
|
-
|
|
552
|
-
let code = 'return schema'
|
|
553
|
-
// If it has a path
|
|
554
|
-
if (ref[1]) {
|
|
555
|
-
// ref[1] could contain a JSON pointer - ex: /definitions/num
|
|
556
|
-
// or plain name fragment id without suffix # - ex: customId
|
|
557
|
-
const walk = ref[1].split('/')
|
|
558
|
-
if (walk.length === 1) {
|
|
559
|
-
const targetId = `#${ref[1]}`
|
|
560
|
-
let dereferenced = idFinder(schema, targetId)
|
|
561
|
-
if (dereferenced === undefined && !ref[0]) {
|
|
562
|
-
// eslint-disable-next-line
|
|
563
|
-
for (var key of Object.keys(externalSchema)) {
|
|
564
|
-
dereferenced = idFinder(externalSchema[key], targetId)
|
|
565
|
-
if (dereferenced !== undefined) {
|
|
566
|
-
root = externalSchema[key]
|
|
567
|
-
break
|
|
568
|
-
}
|
|
569
|
-
}
|
|
570
|
-
}
|
|
571
|
-
|
|
572
|
-
return {
|
|
573
|
-
schema: dereferenced,
|
|
574
|
-
root,
|
|
575
|
-
externalSchema
|
|
576
|
-
}
|
|
577
|
-
} else {
|
|
578
|
-
// eslint-disable-next-line
|
|
579
|
-
for (var i = 1; i < walk.length; i++) {
|
|
580
|
-
code += `[${JSON.stringify(walk[i])}]`
|
|
581
|
-
}
|
|
582
|
-
}
|
|
583
|
-
}
|
|
584
|
-
let result
|
|
585
|
-
try {
|
|
586
|
-
result = (new Function('schema', code))(root)
|
|
587
|
-
} catch (err) {}
|
|
588
|
-
|
|
589
|
-
if (result === undefined && ref[1]) {
|
|
590
|
-
const walk = ref[1].split('/')
|
|
591
|
-
findBadKey(schema, walk.slice(1))
|
|
592
|
-
}
|
|
593
|
-
|
|
594
|
-
if (result.$ref) {
|
|
595
|
-
return refFinder(result.$ref, {
|
|
596
|
-
schema,
|
|
597
|
-
root,
|
|
598
|
-
externalSchema
|
|
599
|
-
})
|
|
600
|
-
}
|
|
601
|
-
|
|
602
|
-
return {
|
|
603
|
-
schema: result,
|
|
604
|
-
root,
|
|
605
|
-
externalSchema
|
|
606
|
-
}
|
|
607
|
-
|
|
608
|
-
function findBadKey (obj, keys) {
|
|
609
|
-
if (keys.length === 0) return null
|
|
610
|
-
const key = keys.shift()
|
|
611
|
-
if (obj[key] === undefined) {
|
|
612
|
-
stringSimilarity = stringSimilarity || require('string-similarity')
|
|
613
|
-
const { bestMatch } = stringSimilarity.findBestMatch(key, Object.keys(obj))
|
|
614
|
-
if (bestMatch.rating >= 0.5) {
|
|
615
|
-
throw new Error(`Cannot find reference ${JSON.stringify(key)}, did you mean ${JSON.stringify(bestMatch.target)}?`)
|
|
616
|
-
} else {
|
|
617
|
-
throw new Error(`Cannot find reference ${JSON.stringify(key)}`)
|
|
618
|
-
}
|
|
619
|
-
}
|
|
620
|
-
return findBadKey(obj[key], keys)
|
|
621
|
-
}
|
|
622
338
|
}
|
|
623
339
|
|
|
624
|
-
function buildCode (location
|
|
340
|
+
function buildCode (location) {
|
|
625
341
|
if (location.schema.$ref) {
|
|
626
|
-
location =
|
|
342
|
+
location = resolveRef(location, location.schema.$ref)
|
|
627
343
|
}
|
|
628
344
|
|
|
629
345
|
const schema = location.schema
|
|
630
346
|
const required = schema.required || []
|
|
631
347
|
|
|
348
|
+
let code = ''
|
|
349
|
+
|
|
350
|
+
const propertiesLocation = mergeLocation(location, 'properties')
|
|
632
351
|
Object.keys(schema.properties || {}).forEach((key) => {
|
|
633
|
-
let propertyLocation = mergeLocation(
|
|
352
|
+
let propertyLocation = mergeLocation(propertiesLocation, key)
|
|
634
353
|
if (schema.properties[key].$ref) {
|
|
635
|
-
propertyLocation =
|
|
354
|
+
propertyLocation = resolveRef(location, schema.properties[key].$ref)
|
|
636
355
|
schema.properties[key] = propertyLocation.schema
|
|
637
356
|
}
|
|
638
357
|
|
|
@@ -648,9 +367,7 @@ function buildCode (location, code, laterCode, locationPath) {
|
|
|
648
367
|
json += ${asString} + ':'
|
|
649
368
|
`
|
|
650
369
|
|
|
651
|
-
|
|
652
|
-
code += result.code
|
|
653
|
-
laterCode = result.laterCode
|
|
370
|
+
code += buildValue(propertyLocation, `obj[${JSON.stringify(key)}]`)
|
|
654
371
|
|
|
655
372
|
const defaultValue = schema.properties[key].default
|
|
656
373
|
if (defaultValue !== undefined) {
|
|
@@ -676,13 +393,18 @@ function buildCode (location, code, laterCode, locationPath) {
|
|
|
676
393
|
code += `if (obj['${requiredProperty}'] === undefined) throw new Error('"${requiredProperty}" is required!')\n`
|
|
677
394
|
}
|
|
678
395
|
|
|
679
|
-
return
|
|
396
|
+
return code
|
|
680
397
|
}
|
|
681
398
|
|
|
682
399
|
function mergeAllOfSchema (location, schema, mergedSchema) {
|
|
683
|
-
|
|
400
|
+
const allOfLocation = mergeLocation(location, 'allOf')
|
|
401
|
+
|
|
402
|
+
for (let i = 0; i < schema.allOf.length; i++) {
|
|
403
|
+
let allOfSchema = schema.allOf[i]
|
|
404
|
+
|
|
684
405
|
if (allOfSchema.$ref) {
|
|
685
|
-
|
|
406
|
+
const allOfSchemaLocation = mergeLocation(allOfLocation, i)
|
|
407
|
+
allOfSchema = resolveRef(allOfSchemaLocation, allOfSchema.$ref).schema
|
|
686
408
|
}
|
|
687
409
|
|
|
688
410
|
let allOfSchemaType = allOfSchema.type
|
|
@@ -769,79 +491,59 @@ function mergeAllOfSchema (location, schema, mergedSchema) {
|
|
|
769
491
|
delete mergedSchema.allOf
|
|
770
492
|
}
|
|
771
493
|
|
|
772
|
-
function buildInnerObject (location
|
|
494
|
+
function buildInnerObject (location) {
|
|
773
495
|
const schema = location.schema
|
|
774
|
-
|
|
496
|
+
let code = buildCode(location)
|
|
775
497
|
if (schema.patternProperties) {
|
|
776
|
-
|
|
777
|
-
result.code += code
|
|
778
|
-
result.laterCode += laterCode
|
|
498
|
+
code += addPatternProperties(location)
|
|
779
499
|
} else if (schema.additionalProperties && !schema.patternProperties) {
|
|
780
|
-
|
|
781
|
-
result.code += code
|
|
782
|
-
result.laterCode += laterCode
|
|
500
|
+
code += addAdditionalProperties(location)
|
|
783
501
|
}
|
|
784
|
-
return
|
|
502
|
+
return code
|
|
785
503
|
}
|
|
786
504
|
|
|
787
|
-
function addIfThenElse (location
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
let innerR
|
|
505
|
+
function addIfThenElse (location) {
|
|
506
|
+
const schema = merge({}, location.schema)
|
|
507
|
+
const thenSchema = schema.then
|
|
508
|
+
const elseSchema = schema.else || { additionalProperties: true }
|
|
792
509
|
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
const then = copy.then
|
|
797
|
-
const e = copy.else ? copy.else : { additionalProperties: true }
|
|
798
|
-
delete copy.if
|
|
799
|
-
delete copy.then
|
|
800
|
-
delete copy.else
|
|
801
|
-
let merged = merge(copy, then)
|
|
802
|
-
let mergedLocation = mergeLocation(location, { schema: merged })
|
|
803
|
-
|
|
804
|
-
const schemaKey = i.$id || randomUUID()
|
|
805
|
-
ajvInstance.addSchema(i, schemaKey)
|
|
510
|
+
delete schema.if
|
|
511
|
+
delete schema.then
|
|
512
|
+
delete schema.else
|
|
806
513
|
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
514
|
+
const ifLocation = mergeLocation(location, 'if')
|
|
515
|
+
const ifSchemaRef = ifLocation.schemaId + ifLocation.jsonPointer
|
|
516
|
+
|
|
517
|
+
let code = `
|
|
518
|
+
if (ajv.validate("${ifSchemaRef}", obj)) {
|
|
810
519
|
`
|
|
811
|
-
if (merged.if && merged.then) {
|
|
812
|
-
innerR = addIfThenElse(mergedLocation, locationPath + 'Then')
|
|
813
|
-
code += innerR.code
|
|
814
|
-
laterCode = innerR.laterCode
|
|
815
|
-
}
|
|
816
520
|
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
laterCode += r.laterCode
|
|
521
|
+
const thenLocation = mergeLocation(location, 'then')
|
|
522
|
+
thenLocation.schema = merge(schema, thenSchema)
|
|
820
523
|
|
|
524
|
+
if (thenSchema.if && thenSchema.then) {
|
|
525
|
+
code += addIfThenElse(thenLocation)
|
|
526
|
+
}
|
|
527
|
+
code += buildInnerObject(thenLocation)
|
|
821
528
|
code += `
|
|
822
529
|
}
|
|
823
530
|
`
|
|
824
|
-
|
|
825
|
-
|
|
531
|
+
|
|
532
|
+
const elseLocation = mergeLocation(location, 'else')
|
|
533
|
+
elseLocation.schema = merge(schema, elseSchema)
|
|
826
534
|
|
|
827
535
|
code += `
|
|
828
536
|
else {
|
|
829
537
|
`
|
|
830
538
|
|
|
831
|
-
if (
|
|
832
|
-
|
|
833
|
-
code += innerR.code
|
|
834
|
-
laterCode += innerR.laterCode
|
|
539
|
+
if (elseSchema.if && elseSchema.then) {
|
|
540
|
+
code += addIfThenElse(elseLocation)
|
|
835
541
|
}
|
|
836
|
-
|
|
837
|
-
r = buildInnerObject(mergedLocation, locationPath + 'Else')
|
|
838
|
-
code += r.code
|
|
839
|
-
laterCode += r.laterCode
|
|
840
|
-
|
|
542
|
+
code += buildInnerObject(elseLocation)
|
|
841
543
|
code += `
|
|
842
544
|
}
|
|
843
545
|
`
|
|
844
|
-
return
|
|
546
|
+
return code
|
|
845
547
|
}
|
|
846
548
|
|
|
847
549
|
function toJSON (variableName) {
|
|
@@ -851,82 +553,63 @@ function toJSON (variableName) {
|
|
|
851
553
|
`
|
|
852
554
|
}
|
|
853
555
|
|
|
854
|
-
function buildObject (location
|
|
556
|
+
function buildObject (location) {
|
|
855
557
|
const schema = location.schema
|
|
856
|
-
|
|
857
|
-
|
|
558
|
+
|
|
559
|
+
if (objectReferenceSerializersMap.has(schema)) {
|
|
560
|
+
return objectReferenceSerializersMap.get(schema)
|
|
858
561
|
}
|
|
859
|
-
|
|
562
|
+
|
|
563
|
+
const functionName = generateFuncName()
|
|
564
|
+
objectReferenceSerializersMap.set(schema, functionName)
|
|
565
|
+
|
|
566
|
+
const schemaId = location.schemaId === rootSchemaId ? '' : location.schemaId
|
|
567
|
+
let functionCode = `
|
|
860
568
|
function ${functionName} (input) {
|
|
861
|
-
// ${
|
|
569
|
+
// ${schemaId + location.jsonPointer}
|
|
862
570
|
`
|
|
863
571
|
if (schema.nullable) {
|
|
864
|
-
|
|
865
|
-
if(input === null) {
|
|
572
|
+
functionCode += `
|
|
573
|
+
if (input === null) {
|
|
866
574
|
return 'null';
|
|
867
575
|
}
|
|
868
576
|
`
|
|
869
577
|
}
|
|
870
578
|
|
|
871
|
-
|
|
872
|
-
code += `
|
|
873
|
-
return ${objectReferenceSerializersMap.get(schema)}(input)
|
|
874
|
-
}
|
|
875
|
-
`
|
|
876
|
-
return code
|
|
877
|
-
}
|
|
878
|
-
objectReferenceSerializersMap.set(schema, functionName)
|
|
879
|
-
|
|
880
|
-
code += `
|
|
579
|
+
functionCode += `
|
|
881
580
|
var obj = ${toJSON('input')}
|
|
882
581
|
var json = '{'
|
|
883
582
|
var addComma = false
|
|
884
583
|
`
|
|
885
584
|
|
|
886
|
-
let
|
|
585
|
+
let rCode
|
|
887
586
|
if (schema.if && schema.then) {
|
|
888
|
-
|
|
889
|
-
var valid
|
|
890
|
-
`
|
|
891
|
-
r = addIfThenElse(location, locationPath)
|
|
587
|
+
rCode = addIfThenElse(location)
|
|
892
588
|
} else {
|
|
893
|
-
|
|
589
|
+
rCode = buildInnerObject(location)
|
|
894
590
|
}
|
|
895
591
|
|
|
896
592
|
// Removes the comma if is the last element of the string (in case there are not properties)
|
|
897
|
-
|
|
593
|
+
functionCode += `${rCode}
|
|
898
594
|
json += '}'
|
|
899
595
|
return json
|
|
900
596
|
}
|
|
901
|
-
${r.laterCode}
|
|
902
597
|
`
|
|
903
598
|
|
|
904
|
-
|
|
599
|
+
contextFunctions.push(functionCode)
|
|
600
|
+
return functionName
|
|
905
601
|
}
|
|
906
602
|
|
|
907
|
-
function buildArray (location
|
|
603
|
+
function buildArray (location) {
|
|
908
604
|
let schema = location.schema
|
|
909
|
-
if (schema.$id !== undefined) {
|
|
910
|
-
schemaReferenceMap.set(schema.$id, schema)
|
|
911
|
-
}
|
|
912
|
-
code += `
|
|
913
|
-
function ${functionName} (obj) {
|
|
914
|
-
// ${locationPath}
|
|
915
|
-
`
|
|
916
|
-
if (schema.nullable) {
|
|
917
|
-
code += `
|
|
918
|
-
if(obj === null) {
|
|
919
|
-
return 'null';
|
|
920
|
-
}
|
|
921
|
-
`
|
|
922
|
-
}
|
|
923
|
-
const laterCode = ''
|
|
924
605
|
|
|
925
606
|
// default to any items type
|
|
926
607
|
if (!schema.items) {
|
|
927
608
|
schema.items = {}
|
|
928
609
|
}
|
|
929
610
|
|
|
611
|
+
let itemsLocation = mergeLocation(location, 'items')
|
|
612
|
+
|
|
930
613
|
if (schema.items.$ref) {
|
|
931
614
|
if (!schema[fjsCloned]) {
|
|
932
615
|
location.schema = clone(location.schema)
|
|
@@ -934,87 +617,108 @@ function buildArray (location, code, functionName, locationPath) {
|
|
|
934
617
|
schema[fjsCloned] = true
|
|
935
618
|
}
|
|
936
619
|
|
|
937
|
-
location =
|
|
620
|
+
location = resolveRef(location, schema.items.$ref)
|
|
621
|
+
itemsLocation = location
|
|
938
622
|
schema.items = location.schema
|
|
623
|
+
}
|
|
939
624
|
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
return ${arrayItemsReferenceSerializersMap.get(schema.items)}(obj)
|
|
943
|
-
}
|
|
944
|
-
`
|
|
945
|
-
return code
|
|
946
|
-
}
|
|
947
|
-
arrayItemsReferenceSerializersMap.set(schema.items, functionName)
|
|
625
|
+
if (arrayItemsReferenceSerializersMap.has(schema.items)) {
|
|
626
|
+
return arrayItemsReferenceSerializersMap.get(schema.items)
|
|
948
627
|
}
|
|
949
628
|
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
if (Array.isArray(schema.items)) {
|
|
953
|
-
result = schema.items.reduce((res, item, i) => {
|
|
954
|
-
const tmpRes = buildValue(laterCode, locationPath + accessor + i, 'obj[i]', mergeLocation(location, { schema: item }))
|
|
955
|
-
const condition = `i === ${i} && ${buildArrayTypeCondition(item.type, accessor)}`
|
|
956
|
-
return {
|
|
957
|
-
code: `${res.code}
|
|
958
|
-
${i > 0 ? 'else' : ''} if (${condition}) {
|
|
959
|
-
${tmpRes.code}
|
|
960
|
-
}`,
|
|
961
|
-
laterCode: `${res.laterCode}
|
|
962
|
-
${tmpRes.laterCode}`
|
|
963
|
-
}
|
|
964
|
-
}, result)
|
|
629
|
+
const functionName = generateFuncName()
|
|
630
|
+
arrayItemsReferenceSerializersMap.set(schema.items, functionName)
|
|
965
631
|
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
}
|
|
972
|
-
`
|
|
973
|
-
}
|
|
632
|
+
const schemaId = location.schemaId === rootSchemaId ? '' : location.schemaId
|
|
633
|
+
let functionCode = `
|
|
634
|
+
function ${functionName} (obj) {
|
|
635
|
+
// ${schemaId + location.jsonPointer}
|
|
636
|
+
`
|
|
974
637
|
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
638
|
+
if (schema.nullable) {
|
|
639
|
+
functionCode += `
|
|
640
|
+
if (obj === null) {
|
|
641
|
+
return 'null';
|
|
642
|
+
}
|
|
979
643
|
`
|
|
980
|
-
} else {
|
|
981
|
-
result = buildValue(laterCode, locationPath + accessor, 'obj[i]', mergeLocation(location, { schema: schema.items }))
|
|
982
644
|
}
|
|
983
645
|
|
|
984
|
-
|
|
646
|
+
functionCode += `
|
|
985
647
|
if (!Array.isArray(obj)) {
|
|
986
648
|
throw new TypeError(\`The value '$\{obj}' does not match schema definition.\`)
|
|
987
649
|
}
|
|
650
|
+
const arrayLength = obj.length
|
|
988
651
|
`
|
|
989
652
|
|
|
990
|
-
|
|
653
|
+
if (!schema.additionalItems) {
|
|
654
|
+
functionCode += `
|
|
655
|
+
if (arrayLength > ${schema.items.length}) {
|
|
656
|
+
throw new Error(\`Item at ${schema.items.length} does not match schema definition.\`)
|
|
657
|
+
}
|
|
658
|
+
`
|
|
659
|
+
}
|
|
660
|
+
|
|
991
661
|
if (largeArrayMechanism !== 'default') {
|
|
992
662
|
if (largeArrayMechanism === 'json-stringify') {
|
|
993
|
-
|
|
663
|
+
functionCode += `if (arrayLength && arrayLength >= ${largeArraySize}) return JSON.stringify(obj)\n`
|
|
994
664
|
} else {
|
|
995
665
|
throw new Error(`Unsupported large array mechanism ${largeArrayMechanism}`)
|
|
996
666
|
}
|
|
997
667
|
}
|
|
998
668
|
|
|
999
|
-
|
|
1000
|
-
let jsonOutput= ''
|
|
1001
|
-
|
|
1002
|
-
let json = ''
|
|
1003
|
-
${result.code}
|
|
1004
|
-
jsonOutput += json
|
|
669
|
+
functionCode += `
|
|
670
|
+
let jsonOutput = ''
|
|
671
|
+
`
|
|
1005
672
|
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
673
|
+
if (Array.isArray(schema.items)) {
|
|
674
|
+
for (let i = 0; i < schema.items.length; i++) {
|
|
675
|
+
const item = schema.items[i]
|
|
676
|
+
const tmpRes = buildValue(mergeLocation(itemsLocation, i), `obj[${i}]`)
|
|
677
|
+
functionCode += `
|
|
678
|
+
if (${i} < arrayLength) {
|
|
679
|
+
if (${buildArrayTypeCondition(item.type, `[${i}]`)}) {
|
|
680
|
+
let json = ''
|
|
681
|
+
${tmpRes}
|
|
682
|
+
jsonOutput += json
|
|
683
|
+
if (${i} < arrayLength - 1) {
|
|
684
|
+
jsonOutput += ','
|
|
685
|
+
}
|
|
686
|
+
} else {
|
|
687
|
+
throw new Error(\`Item at ${i} does not match schema definition.\`)
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
`
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
if (schema.additionalItems) {
|
|
694
|
+
functionCode += `
|
|
695
|
+
for (let i = ${schema.items.length}; i < arrayLength; i++) {
|
|
696
|
+
let json = JSON.stringify(obj[i])
|
|
697
|
+
jsonOutput += json
|
|
698
|
+
if (i < arrayLength - 1) {
|
|
699
|
+
jsonOutput += ','
|
|
700
|
+
}
|
|
701
|
+
}`
|
|
1009
702
|
}
|
|
703
|
+
} else {
|
|
704
|
+
const code = buildValue(itemsLocation, 'obj[i]')
|
|
705
|
+
functionCode += `
|
|
706
|
+
for (let i = 0; i < arrayLength; i++) {
|
|
707
|
+
let json = ''
|
|
708
|
+
${code}
|
|
709
|
+
jsonOutput += json
|
|
710
|
+
if (i < arrayLength - 1) {
|
|
711
|
+
jsonOutput += ','
|
|
712
|
+
}
|
|
713
|
+
}`
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
functionCode += `
|
|
1010
717
|
return \`[\${jsonOutput}]\`
|
|
1011
718
|
}`
|
|
1012
719
|
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
`
|
|
1016
|
-
|
|
1017
|
-
return code
|
|
720
|
+
contextFunctions.push(functionCode)
|
|
721
|
+
return functionName
|
|
1018
722
|
}
|
|
1019
723
|
|
|
1020
724
|
function buildArrayTypeCondition (type, accessor) {
|
|
@@ -1054,40 +758,17 @@ function buildArrayTypeCondition (type, accessor) {
|
|
|
1054
758
|
return condition
|
|
1055
759
|
}
|
|
1056
760
|
|
|
1057
|
-
function dereferenceOfRefs (location, type) {
|
|
1058
|
-
if (!location.schema[fjsCloned]) {
|
|
1059
|
-
const schemaClone = clone(location.schema)
|
|
1060
|
-
schemaClone[fjsCloned] = true
|
|
1061
|
-
location.schema = schemaClone
|
|
1062
|
-
}
|
|
1063
|
-
|
|
1064
|
-
const schema = location.schema
|
|
1065
|
-
const locations = []
|
|
1066
|
-
|
|
1067
|
-
schema[type].forEach((s, index) => {
|
|
1068
|
-
// follow the refs
|
|
1069
|
-
let sLocation = mergeLocation(location, { schema: s })
|
|
1070
|
-
while (s.$ref) {
|
|
1071
|
-
sLocation = refFinder(s.$ref, sLocation)
|
|
1072
|
-
schema[type][index] = sLocation.schema
|
|
1073
|
-
s = schema[type][index]
|
|
1074
|
-
}
|
|
1075
|
-
locations[index] = sLocation
|
|
1076
|
-
})
|
|
1077
|
-
|
|
1078
|
-
return locations
|
|
1079
|
-
}
|
|
1080
|
-
|
|
1081
761
|
let genFuncNameCounter = 0
|
|
1082
762
|
function generateFuncName () {
|
|
1083
763
|
return 'anonymous' + genFuncNameCounter++
|
|
1084
764
|
}
|
|
1085
765
|
|
|
1086
|
-
function buildValue (
|
|
766
|
+
function buildValue (location, input) {
|
|
1087
767
|
let schema = location.schema
|
|
1088
768
|
|
|
1089
769
|
if (schema.$ref) {
|
|
1090
|
-
|
|
770
|
+
location = resolveRef(location, schema.$ref)
|
|
771
|
+
schema = location.schema
|
|
1091
772
|
}
|
|
1092
773
|
|
|
1093
774
|
if (schema.type === undefined) {
|
|
@@ -1134,44 +815,32 @@ function buildValue (laterCode, locationPath, input, location) {
|
|
|
1134
815
|
code += `json += ${funcName}(${input})`
|
|
1135
816
|
break
|
|
1136
817
|
case 'object':
|
|
1137
|
-
funcName =
|
|
1138
|
-
laterCode = buildObject(location, laterCode, funcName, locationPath)
|
|
818
|
+
funcName = buildObject(location)
|
|
1139
819
|
code += `json += ${funcName}(${input})`
|
|
1140
820
|
break
|
|
1141
821
|
case 'array':
|
|
1142
|
-
funcName =
|
|
1143
|
-
laterCode = buildArray(location, laterCode, funcName, locationPath)
|
|
822
|
+
funcName = buildArray(location)
|
|
1144
823
|
code += `json += ${funcName}(${input})`
|
|
1145
824
|
break
|
|
1146
825
|
case undefined:
|
|
1147
|
-
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) {
|
|
1148
831
|
// beware: dereferenceOfRefs has side effects and changes schema.anyOf
|
|
1149
|
-
const
|
|
1150
|
-
|
|
1151
|
-
const nestedResult = buildValue(laterCode, locationPath + 'i' + index, input, location)
|
|
1152
|
-
// Since we are only passing the relevant schema to ajv.validate, it needs to be full dereferenced
|
|
1153
|
-
// otherwise any $ref pointing to an external schema would result in an error.
|
|
1154
|
-
// Full dereference of the schema happens as side effect of two functions:
|
|
1155
|
-
// 1. `dereferenceOfRefs` loops through the `schema.anyOf`` array and replaces any top level reference
|
|
1156
|
-
// with the actual schema
|
|
1157
|
-
// 2. `buildValue`, through `buildCode`, replaces any reference in object properties with the actual schema
|
|
1158
|
-
// (see https://github.com/fastify/fast-json-stringify/blob/6da3b3e8ac24b1ca5578223adedb4083b7adf8db/index.js#L631)
|
|
1159
|
-
|
|
1160
|
-
// Ajv does not support js date format. In order to properly validate objects containing a date,
|
|
1161
|
-
// it needs to replace all occurrences of the string date format with a custom keyword fjs_date_type.
|
|
1162
|
-
// (see https://github.com/fastify/fast-json-stringify/pull/441)
|
|
1163
|
-
const extendedSchema = clone(location.schema)
|
|
1164
|
-
extendDateTimeType(extendedSchema)
|
|
1165
|
-
|
|
1166
|
-
const schemaKey = location.schema.$id || randomUUID()
|
|
1167
|
-
ajvInstance.addSchema(extendedSchema, schemaKey)
|
|
832
|
+
const type = schema.anyOf ? 'anyOf' : 'oneOf'
|
|
833
|
+
const anyOfLocation = mergeLocation(location, type)
|
|
1168
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)
|
|
1169
839
|
code += `
|
|
1170
|
-
${index === 0 ? 'if' : 'else if'}(ajv.validate("${
|
|
1171
|
-
${nestedResult
|
|
840
|
+
${index === 0 ? 'if' : 'else if'}(ajv.validate("${schemaRef}", ${input}))
|
|
841
|
+
${nestedResult}
|
|
1172
842
|
`
|
|
1173
|
-
|
|
1174
|
-
})
|
|
843
|
+
}
|
|
1175
844
|
|
|
1176
845
|
code += `
|
|
1177
846
|
else throw new Error(\`The value $\{JSON.stringify(${input})} does not match schema definition.\`)
|
|
@@ -1208,41 +877,41 @@ function buildValue (laterCode, locationPath, input, location) {
|
|
|
1208
877
|
} else {`
|
|
1209
878
|
}
|
|
1210
879
|
|
|
880
|
+
const locationClone = clone(location)
|
|
1211
881
|
sortedTypes.forEach((type, index) => {
|
|
1212
882
|
const statement = index === 0 ? 'if' : 'else if'
|
|
1213
|
-
|
|
1214
|
-
const nestedResult = buildValue(
|
|
883
|
+
locationClone.schema.type = type
|
|
884
|
+
const nestedResult = buildValue(locationClone, input)
|
|
1215
885
|
switch (type) {
|
|
1216
886
|
case 'string': {
|
|
1217
887
|
code += `
|
|
1218
888
|
${statement}(${input} === null || typeof ${input} === "${type}" || ${input} instanceof Date || ${input} instanceof RegExp || (typeof ${input} === "object" && Object.hasOwnProperty.call(${input}, "toString")))
|
|
1219
|
-
${nestedResult
|
|
889
|
+
${nestedResult}
|
|
1220
890
|
`
|
|
1221
891
|
break
|
|
1222
892
|
}
|
|
1223
893
|
case 'array': {
|
|
1224
894
|
code += `
|
|
1225
895
|
${statement}(Array.isArray(${input}))
|
|
1226
|
-
${nestedResult
|
|
896
|
+
${nestedResult}
|
|
1227
897
|
`
|
|
1228
898
|
break
|
|
1229
899
|
}
|
|
1230
900
|
case 'integer': {
|
|
1231
901
|
code += `
|
|
1232
902
|
${statement}(Number.isInteger(${input}) || ${input} === null)
|
|
1233
|
-
${nestedResult
|
|
903
|
+
${nestedResult}
|
|
1234
904
|
`
|
|
1235
905
|
break
|
|
1236
906
|
}
|
|
1237
907
|
default: {
|
|
1238
908
|
code += `
|
|
1239
909
|
${statement}(typeof ${input} === "${type}" || ${input} === null)
|
|
1240
|
-
${nestedResult
|
|
910
|
+
${nestedResult}
|
|
1241
911
|
`
|
|
1242
912
|
break
|
|
1243
913
|
}
|
|
1244
914
|
}
|
|
1245
|
-
laterCode = nestedResult.laterCode
|
|
1246
915
|
})
|
|
1247
916
|
code += `
|
|
1248
917
|
else throw new Error(\`The value $\{JSON.stringify(${input})} does not match schema definition.\`)
|
|
@@ -1258,10 +927,15 @@ function buildValue (laterCode, locationPath, input, location) {
|
|
|
1258
927
|
}
|
|
1259
928
|
}
|
|
1260
929
|
|
|
1261
|
-
return
|
|
930
|
+
return code
|
|
1262
931
|
}
|
|
1263
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)
|
|
1264
936
|
function extendDateTimeType (schema) {
|
|
937
|
+
if (schema === null) return
|
|
938
|
+
|
|
1265
939
|
if (schema.type === 'string' && ['date-time', 'date', 'time'].includes(schema.format)) {
|
|
1266
940
|
schema.fjs_date_type = schema.format
|
|
1267
941
|
delete schema.type
|