fast-json-stringify 3.2.0 → 4.2.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/.github/workflows/ci.yml +3 -33
- package/README.md +37 -13
- package/ajv.js +37 -0
- package/benchmark/bench-cmp-branch.js +114 -0
- package/{bench.js → benchmark/bench-cmp-lib.js} +4 -4
- package/benchmark/bench-thread.js +21 -0
- package/benchmark/bench.js +251 -0
- package/example.js +0 -3
- package/index.d.ts +25 -1
- package/index.js +362 -680
- package/package.json +10 -4
- package/serializer.js +168 -0
- package/standalone.js +42 -0
- package/test/additionalProperties.test.js +23 -19
- package/test/allof.test.js +56 -0
- package/test/anyof.test.js +88 -9
- package/test/array.test.js +110 -23
- package/test/basic.test.js +8 -8
- package/test/date.test.js +62 -35
- package/test/debug-mode.test.js +1 -1
- package/test/fixtures/.keep +0 -0
- package/test/nestedObjects.test.js +31 -0
- package/test/nullable.test.js +328 -0
- package/test/oneof.test.js +9 -22
- package/test/patternProperties.test.js +17 -6
- package/test/required.test.js +1 -1
- package/test/sanitize3.test.js +9 -11
- package/test/standalone-mode.test.js +121 -0
- package/test/types/test.ts +9 -1
- package/test/typesArray.test.js +40 -3
package/index.js
CHANGED
|
@@ -2,15 +2,14 @@
|
|
|
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
15
|
let stringSimilarity = null
|
|
@@ -55,16 +54,17 @@ const objectReferenceSerializersMap = new Map()
|
|
|
55
54
|
const schemaReferenceMap = new Map()
|
|
56
55
|
|
|
57
56
|
let ajvInstance = null
|
|
57
|
+
let contextFunctions = null
|
|
58
58
|
|
|
59
59
|
function build (schema, options) {
|
|
60
60
|
arrayItemsReferenceSerializersMap.clear()
|
|
61
61
|
objectReferenceSerializersMap.clear()
|
|
62
62
|
schemaReferenceMap.clear()
|
|
63
63
|
|
|
64
|
+
contextFunctions = []
|
|
64
65
|
options = options || {}
|
|
65
66
|
|
|
66
|
-
ajvInstance =
|
|
67
|
-
ajvFormats(ajvInstance)
|
|
67
|
+
ajvInstance = buildAjv(options.ajv)
|
|
68
68
|
|
|
69
69
|
isValidSchema(schema)
|
|
70
70
|
if (options.schema) {
|
|
@@ -74,11 +74,8 @@ function build (schema, options) {
|
|
|
74
74
|
}
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
-
let intParseFunctionName = 'trunc'
|
|
78
77
|
if (options.rounding) {
|
|
79
|
-
if (['floor', 'ceil', 'round'].includes(options.rounding)) {
|
|
80
|
-
intParseFunctionName = options.rounding
|
|
81
|
-
} else {
|
|
78
|
+
if (!['floor', 'ceil', 'round'].includes(options.rounding)) {
|
|
82
79
|
throw new Error(`Unsupported integer rounding method ${options.rounding}`)
|
|
83
80
|
}
|
|
84
81
|
}
|
|
@@ -99,15 +96,7 @@ function build (schema, options) {
|
|
|
99
96
|
}
|
|
100
97
|
}
|
|
101
98
|
|
|
102
|
-
|
|
103
|
-
let code = `
|
|
104
|
-
'use strict'
|
|
105
|
-
`
|
|
106
|
-
|
|
107
|
-
code += `
|
|
108
|
-
${asFunctions}
|
|
109
|
-
function parseInteger(int) { return Math.${intParseFunctionName}(int) }
|
|
110
|
-
`
|
|
99
|
+
const serializer = new Serializer(options)
|
|
111
100
|
|
|
112
101
|
let location = {
|
|
113
102
|
schema,
|
|
@@ -124,62 +113,45 @@ function build (schema, options) {
|
|
|
124
113
|
schema.type = inferTypeByKeyword(schema)
|
|
125
114
|
}
|
|
126
115
|
|
|
127
|
-
|
|
116
|
+
const code = buildValue('main', 'input', location)
|
|
128
117
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
code
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
main = schema.nullable ? '$asIntegerNullable' : '$asInteger'
|
|
139
|
-
break
|
|
140
|
-
case 'number':
|
|
141
|
-
main = schema.nullable ? '$asNumberNullable' : '$asNumber'
|
|
142
|
-
break
|
|
143
|
-
case 'boolean':
|
|
144
|
-
main = schema.nullable ? '$asBooleanNullable' : '$asBoolean'
|
|
145
|
-
break
|
|
146
|
-
case 'null':
|
|
147
|
-
main = '$asNull'
|
|
148
|
-
break
|
|
149
|
-
case 'array':
|
|
150
|
-
main = '$main'
|
|
151
|
-
code = buildArray(location, code, main)
|
|
152
|
-
schema = location.schema
|
|
153
|
-
break
|
|
154
|
-
case undefined:
|
|
155
|
-
main = '$asAny'
|
|
156
|
-
break
|
|
157
|
-
default:
|
|
158
|
-
throw new Error(`${schema.type} unsupported`)
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
code += `
|
|
162
|
-
;
|
|
163
|
-
return ${main}
|
|
164
|
-
`
|
|
165
|
-
const dependencies = [ajvInstance]
|
|
166
|
-
const dependenciesName = ['ajv']
|
|
167
|
-
ajvInstance = null
|
|
118
|
+
const contextFunctionCode = `
|
|
119
|
+
function main (input) {
|
|
120
|
+
let json = ''
|
|
121
|
+
${code}
|
|
122
|
+
return json
|
|
123
|
+
}
|
|
124
|
+
${contextFunctions.join('\n')}
|
|
125
|
+
return main
|
|
126
|
+
`
|
|
168
127
|
|
|
169
|
-
dependenciesName
|
|
128
|
+
const dependenciesName = ['ajv', 'serializer', contextFunctionCode]
|
|
170
129
|
|
|
171
130
|
if (options.debugMode) {
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
131
|
+
options.mode = 'debug'
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (options.mode === 'debug') {
|
|
135
|
+
return { code: dependenciesName.join('\n'), ajv: ajvInstance }
|
|
176
136
|
}
|
|
177
137
|
|
|
138
|
+
if (options.mode === 'standalone') {
|
|
139
|
+
// lazy load
|
|
140
|
+
const buildStandaloneCode = require('./standalone')
|
|
141
|
+
return buildStandaloneCode(options, ajvInstance, contextFunctionCode)
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/* eslint no-new-func: "off" */
|
|
145
|
+
const contextFunc = new Function('ajv', 'serializer', contextFunctionCode)
|
|
146
|
+
const stringifyFunc = contextFunc(ajvInstance, serializer)
|
|
147
|
+
|
|
148
|
+
ajvInstance = null
|
|
149
|
+
contextFunctions = null
|
|
178
150
|
arrayItemsReferenceSerializersMap.clear()
|
|
179
151
|
objectReferenceSerializersMap.clear()
|
|
180
152
|
schemaReferenceMap.clear()
|
|
181
153
|
|
|
182
|
-
return
|
|
154
|
+
return stringifyFunc
|
|
183
155
|
}
|
|
184
156
|
|
|
185
157
|
const objectKeywords = [
|
|
@@ -239,170 +211,14 @@ function inferTypeByKeyword (schema) {
|
|
|
239
211
|
return schema.type
|
|
240
212
|
}
|
|
241
213
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
function getStringSerializer (format) {
|
|
249
|
-
return stringSerializerMap[format] ||
|
|
250
|
-
'$asString'
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
function getTestSerializer (format) {
|
|
254
|
-
return stringSerializerMap[format]
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
const asFunctions = `
|
|
258
|
-
function $pad2Zeros (num) {
|
|
259
|
-
const s = '00' + num
|
|
260
|
-
return s[s.length - 2] + s[s.length - 1]
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
function $asAny (i) {
|
|
264
|
-
return JSON.stringify(i)
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
function $asNull () {
|
|
268
|
-
return 'null'
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
function $asInteger (i) {
|
|
272
|
-
if (typeof i === 'bigint') {
|
|
273
|
-
return i.toString()
|
|
274
|
-
} else if (Number.isInteger(i)) {
|
|
275
|
-
return $asNumber(i)
|
|
276
|
-
} else {
|
|
277
|
-
/* eslint no-undef: "off" */
|
|
278
|
-
return $asNumber(parseInteger(i))
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
function $asIntegerNullable (i) {
|
|
283
|
-
return i === null ? null : $asInteger(i)
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
function $asNumber (i) {
|
|
287
|
-
const num = Number(i)
|
|
288
|
-
if (isNaN(num)) {
|
|
289
|
-
return 'null'
|
|
290
|
-
} else {
|
|
291
|
-
return '' + num
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
function $asNumberNullable (i) {
|
|
296
|
-
return i === null ? null : $asNumber(i)
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
function $asBoolean (bool) {
|
|
300
|
-
return bool && 'true' || 'false' // eslint-disable-line
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
function $asBooleanNullable (bool) {
|
|
304
|
-
return bool === null ? null : $asBoolean(bool)
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
function $asDatetime (date, skipQuotes) {
|
|
308
|
-
const quotes = skipQuotes === true ? '' : '"'
|
|
309
|
-
if (date instanceof Date) {
|
|
310
|
-
return quotes + date.toISOString() + quotes
|
|
311
|
-
} else if (date && typeof date.toISOString === 'function') {
|
|
312
|
-
return quotes + date.toISOString() + quotes
|
|
313
|
-
} else {
|
|
314
|
-
return $asString(date, skipQuotes)
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
function $asDate (date, skipQuotes) {
|
|
319
|
-
const quotes = skipQuotes === true ? '' : '"'
|
|
320
|
-
if (date instanceof Date) {
|
|
321
|
-
return quotes + new Date(date.getTime() - (date.getTimezoneOffset() * 60000 )).toISOString().slice(0, 10) + quotes
|
|
322
|
-
} else if (date && typeof date.format === 'function') {
|
|
323
|
-
return quotes + date.format('YYYY-MM-DD') + quotes
|
|
324
|
-
} else {
|
|
325
|
-
return $asString(date, skipQuotes)
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
function $asTime (date, skipQuotes) {
|
|
330
|
-
const quotes = skipQuotes === true ? '' : '"'
|
|
331
|
-
if (date instanceof Date) {
|
|
332
|
-
const hour = new Intl.DateTimeFormat('en', { hour: 'numeric', hour12: false }).format(date)
|
|
333
|
-
const minute = new Intl.DateTimeFormat('en', { minute: 'numeric' }).format(date)
|
|
334
|
-
const second = new Intl.DateTimeFormat('en', { second: 'numeric' }).format(date)
|
|
335
|
-
return quotes + $pad2Zeros(hour) + ':' + $pad2Zeros(minute) + ':' + $pad2Zeros(second) + quotes
|
|
336
|
-
} else if (date && typeof date.format === 'function') {
|
|
337
|
-
return quotes + date.format('HH:mm:ss') + quotes
|
|
338
|
-
} else {
|
|
339
|
-
return $asString(date, skipQuotes)
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
function $asString (str, skipQuotes) {
|
|
344
|
-
const quotes = skipQuotes === true ? '' : '"'
|
|
345
|
-
if (str instanceof Date) {
|
|
346
|
-
return quotes + str.toISOString() + quotes
|
|
347
|
-
} else if (str === null) {
|
|
348
|
-
return quotes + quotes
|
|
349
|
-
} else if (str instanceof RegExp) {
|
|
350
|
-
str = str.source
|
|
351
|
-
} else if (typeof str !== 'string') {
|
|
352
|
-
str = str.toString()
|
|
353
|
-
}
|
|
354
|
-
// If we skipQuotes it means that we are using it as test
|
|
355
|
-
// no need to test the string length for the render
|
|
356
|
-
if (skipQuotes) {
|
|
357
|
-
return str
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
if (str.length < 42) {
|
|
361
|
-
return $asStringSmall(str)
|
|
362
|
-
} else {
|
|
363
|
-
return JSON.stringify(str)
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
function $asStringNullable (str) {
|
|
368
|
-
return str === null ? null : $asString(str)
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
// magically escape strings for json
|
|
372
|
-
// relying on their charCodeAt
|
|
373
|
-
// everything below 32 needs JSON.stringify()
|
|
374
|
-
// every string that contain surrogate needs JSON.stringify()
|
|
375
|
-
// 34 and 92 happens all the time, so we
|
|
376
|
-
// have a fast case for them
|
|
377
|
-
function $asStringSmall (str) {
|
|
378
|
-
const l = str.length
|
|
379
|
-
let result = ''
|
|
380
|
-
let last = 0
|
|
381
|
-
let found = false
|
|
382
|
-
let surrogateFound = false
|
|
383
|
-
let point = 255
|
|
384
|
-
// eslint-disable-next-line
|
|
385
|
-
for (var i = 0; i < l && point >= 32; i++) {
|
|
386
|
-
point = str.charCodeAt(i)
|
|
387
|
-
if (point >= 0xD800 && point <= 0xDFFF) {
|
|
388
|
-
// The current character is a surrogate.
|
|
389
|
-
surrogateFound = true
|
|
390
|
-
}
|
|
391
|
-
if (point === 34 || point === 92) {
|
|
392
|
-
result += str.slice(last, i) + '\\\\'
|
|
393
|
-
last = i
|
|
394
|
-
found = true
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
if (!found) {
|
|
399
|
-
result = str
|
|
400
|
-
} else {
|
|
401
|
-
result += str.slice(last)
|
|
214
|
+
function getStringSerializer (format, nullable) {
|
|
215
|
+
switch (format) {
|
|
216
|
+
case 'date-time': return nullable ? 'serializer.asDatetimeNullable.bind(serializer)' : 'serializer.asDatetime.bind(serializer)'
|
|
217
|
+
case 'date': return nullable ? 'serializer.asDateNullable.bind(serializer)' : 'serializer.asDate.bind(serializer)'
|
|
218
|
+
case 'time': return nullable ? 'serializer.asTimeNullable.bind(serializer)' : 'serializer.asTime.bind(serializer)'
|
|
219
|
+
default: return nullable ? 'serializer.asStringNullable.bind(serializer)' : 'serializer.asString.bind(serializer)'
|
|
402
220
|
}
|
|
403
|
-
return ((point < 32) || (surrogateFound === true)) ? JSON.stringify(str) : '"' + result + '"'
|
|
404
221
|
}
|
|
405
|
-
`
|
|
406
222
|
|
|
407
223
|
function addPatternProperties (location) {
|
|
408
224
|
const schema = location.schema
|
|
@@ -413,81 +229,28 @@ function addPatternProperties (location) {
|
|
|
413
229
|
for (var i = 0; i < keys.length; i++) {
|
|
414
230
|
if (properties[keys[i]]) continue
|
|
415
231
|
`
|
|
232
|
+
|
|
416
233
|
Object.keys(pp).forEach((regex, index) => {
|
|
417
234
|
let ppLocation = mergeLocation(location, { schema: pp[regex] })
|
|
418
235
|
if (pp[regex].$ref) {
|
|
419
236
|
ppLocation = refFinder(pp[regex].$ref, location)
|
|
420
237
|
pp[regex] = ppLocation.schema
|
|
421
238
|
}
|
|
422
|
-
|
|
423
|
-
const format = pp[regex].format
|
|
424
|
-
const stringSerializer = getStringSerializer(format)
|
|
239
|
+
|
|
425
240
|
try {
|
|
426
241
|
RegExp(regex)
|
|
427
242
|
} catch (err) {
|
|
428
243
|
throw new Error(`${err.message}. Found at ${regex} matching ${JSON.stringify(pp[regex])}`)
|
|
429
244
|
}
|
|
430
245
|
|
|
431
|
-
const
|
|
432
|
-
|
|
433
|
-
if (type === 'object') {
|
|
434
|
-
code += `${buildObject(ppLocation, '', 'buildObjectPP' + index)}
|
|
435
|
-
${ifPpKeyExists}
|
|
436
|
-
${addComma}
|
|
437
|
-
json += $asString(keys[i]) + ':' + buildObjectPP${index}(obj[keys[i]])
|
|
438
|
-
`
|
|
439
|
-
} else if (type === 'array') {
|
|
440
|
-
code += `${buildArray(ppLocation, '', 'buildArrayPP' + index)}
|
|
441
|
-
${ifPpKeyExists}
|
|
442
|
-
${addComma}
|
|
443
|
-
json += $asString(keys[i]) + ':' + buildArrayPP${index}(obj[keys[i]])
|
|
444
|
-
`
|
|
445
|
-
} else if (type === 'null') {
|
|
446
|
-
code += `
|
|
447
|
-
${ifPpKeyExists}
|
|
448
|
-
${addComma}
|
|
449
|
-
json += $asString(keys[i]) +':null'
|
|
450
|
-
`
|
|
451
|
-
} else if (type === 'string') {
|
|
452
|
-
code += `
|
|
453
|
-
${ifPpKeyExists}
|
|
454
|
-
${addComma}
|
|
455
|
-
json += $asString(keys[i]) + ':' + ${stringSerializer}(obj[keys[i]])
|
|
456
|
-
`
|
|
457
|
-
} else if (type === 'integer') {
|
|
458
|
-
code += `
|
|
459
|
-
${ifPpKeyExists}
|
|
460
|
-
${addComma}
|
|
461
|
-
json += $asString(keys[i]) + ':' + $asInteger(obj[keys[i]])
|
|
462
|
-
`
|
|
463
|
-
} else if (type === 'number') {
|
|
464
|
-
code += `
|
|
465
|
-
${ifPpKeyExists}
|
|
466
|
-
${addComma}
|
|
467
|
-
json += $asString(keys[i]) + ':' + $asNumber(obj[keys[i]])
|
|
468
|
-
`
|
|
469
|
-
} else if (type === 'boolean') {
|
|
470
|
-
code += `
|
|
471
|
-
${ifPpKeyExists}
|
|
472
|
-
${addComma}
|
|
473
|
-
json += $asString(keys[i]) + ':' + $asBoolean(obj[keys[i]])
|
|
474
|
-
`
|
|
475
|
-
} else if (type === undefined) {
|
|
476
|
-
code += `
|
|
477
|
-
${ifPpKeyExists}
|
|
478
|
-
${addComma}
|
|
479
|
-
json += $asString(keys[i]) + ':' + $asAny(obj[keys[i]])
|
|
480
|
-
`
|
|
481
|
-
} else {
|
|
482
|
-
code += `
|
|
483
|
-
${ifPpKeyExists}
|
|
484
|
-
throw new Error('Cannot coerce ' + obj[keys[i]] + ' to ' + ${JSON.stringify(type)})
|
|
485
|
-
`
|
|
486
|
-
}
|
|
487
|
-
|
|
246
|
+
const valueCode = buildValue('', 'obj[keys[i]]', ppLocation)
|
|
488
247
|
code += `
|
|
489
|
-
|
|
490
|
-
}
|
|
248
|
+
if (/${regex.replace(/\\*\//g, '\\/')}/.test(keys[i])) {
|
|
249
|
+
${addComma}
|
|
250
|
+
json += serializer.asString(keys[i]) + ':'
|
|
251
|
+
${valueCode}
|
|
252
|
+
continue
|
|
253
|
+
}
|
|
491
254
|
`
|
|
492
255
|
})
|
|
493
256
|
if (schema.additionalProperties) {
|
|
@@ -504,12 +267,14 @@ function additionalProperty (location) {
|
|
|
504
267
|
let ap = location.schema.additionalProperties
|
|
505
268
|
let code = ''
|
|
506
269
|
if (ap === true) {
|
|
507
|
-
|
|
270
|
+
code += `
|
|
508
271
|
if (obj[keys[i]] !== undefined && typeof obj[keys[i]] !== 'function' && typeof obj[keys[i]] !== 'symbol') {
|
|
509
272
|
${addComma}
|
|
510
|
-
json +=
|
|
273
|
+
json += serializer.asString(keys[i]) + ':' + JSON.stringify(obj[keys[i]])
|
|
511
274
|
}
|
|
512
275
|
`
|
|
276
|
+
|
|
277
|
+
return code
|
|
513
278
|
}
|
|
514
279
|
let apLocation = mergeLocation(location, { schema: ap })
|
|
515
280
|
if (ap.$ref) {
|
|
@@ -517,60 +282,14 @@ function additionalProperty (location) {
|
|
|
517
282
|
ap = apLocation.schema
|
|
518
283
|
}
|
|
519
284
|
|
|
520
|
-
const
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
} else if (type === 'array') {
|
|
529
|
-
code += `${buildArray(apLocation, '', 'buildArrayAP')}
|
|
530
|
-
${addComma}
|
|
531
|
-
json += $asString(keys[i]) + ':' + buildArrayAP(obj[keys[i]])
|
|
532
|
-
`
|
|
533
|
-
} else if (type === 'null') {
|
|
534
|
-
code += `
|
|
535
|
-
${addComma}
|
|
536
|
-
json += $asString(keys[i]) +':null'
|
|
537
|
-
`
|
|
538
|
-
} else if (type === 'string') {
|
|
539
|
-
code += `
|
|
540
|
-
${addComma}
|
|
541
|
-
json += $asString(keys[i]) + ':' + ${stringSerializer}(obj[keys[i]])
|
|
542
|
-
`
|
|
543
|
-
} else if (type === 'integer') {
|
|
544
|
-
code += `
|
|
545
|
-
var t = Number(obj[keys[i]])
|
|
546
|
-
if (!isNaN(t)) {
|
|
547
|
-
${addComma}
|
|
548
|
-
json += $asString(keys[i]) + ':' + t
|
|
549
|
-
}
|
|
550
|
-
`
|
|
551
|
-
} else if (type === 'number') {
|
|
552
|
-
code += `
|
|
553
|
-
var t = Number(obj[keys[i]])
|
|
554
|
-
if (!isNaN(t)) {
|
|
555
|
-
${addComma}
|
|
556
|
-
json += $asString(keys[i]) + ':' + t
|
|
557
|
-
}
|
|
558
|
-
`
|
|
559
|
-
} else if (type === 'boolean') {
|
|
560
|
-
code += `
|
|
561
|
-
${addComma}
|
|
562
|
-
json += $asString(keys[i]) + ':' + $asBoolean(obj[keys[i]])
|
|
563
|
-
`
|
|
564
|
-
} else if (type === undefined) {
|
|
565
|
-
code += `
|
|
566
|
-
${addComma}
|
|
567
|
-
json += $asString(keys[i]) + ':' + $asAny(obj[keys[i]])
|
|
568
|
-
`
|
|
569
|
-
} else {
|
|
570
|
-
code += `
|
|
571
|
-
throw new Error('Cannot coerce ' + obj[keys[i]] + ' to ' + ${JSON.stringify(type)})
|
|
572
|
-
`
|
|
573
|
-
}
|
|
285
|
+
const valueCode = buildValue('', 'obj[keys[i]]', apLocation)
|
|
286
|
+
|
|
287
|
+
code += `
|
|
288
|
+
${addComma}
|
|
289
|
+
json += serializer.asString(keys[i]) + ':'
|
|
290
|
+
${valueCode}
|
|
291
|
+
`
|
|
292
|
+
|
|
574
293
|
return code
|
|
575
294
|
}
|
|
576
295
|
|
|
@@ -716,15 +435,17 @@ function refFinder (ref, location) {
|
|
|
716
435
|
}
|
|
717
436
|
}
|
|
718
437
|
|
|
719
|
-
function buildCode (location,
|
|
438
|
+
function buildCode (location, locationPath) {
|
|
720
439
|
if (location.schema.$ref) {
|
|
721
440
|
location = refFinder(location.schema.$ref, location)
|
|
722
441
|
}
|
|
723
442
|
|
|
724
443
|
const schema = location.schema
|
|
725
|
-
|
|
444
|
+
const required = schema.required || []
|
|
726
445
|
|
|
727
|
-
|
|
446
|
+
let code = ''
|
|
447
|
+
|
|
448
|
+
Object.keys(schema.properties || {}).forEach((key) => {
|
|
728
449
|
let propertyLocation = mergeLocation(location, { schema: schema.properties[key] })
|
|
729
450
|
if (schema.properties[key].$ref) {
|
|
730
451
|
propertyLocation = refFinder(schema.properties[key].$ref, location)
|
|
@@ -734,61 +455,25 @@ function buildCode (location, code, laterCode, name) {
|
|
|
734
455
|
// Using obj['key'] !== undefined instead of obj.hasOwnProperty(prop) for perf reasons,
|
|
735
456
|
// see https://github.com/mcollina/fast-json-stringify/pull/3 for discussion.
|
|
736
457
|
|
|
737
|
-
const type = schema.properties[key].type
|
|
738
|
-
const nullable = schema.properties[key].nullable
|
|
739
458
|
const sanitized = JSON.stringify(key)
|
|
740
459
|
const asString = JSON.stringify(sanitized)
|
|
741
460
|
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
json += ${asString} + ':null'
|
|
747
|
-
var rendered = true
|
|
748
|
-
} else {
|
|
749
|
-
`
|
|
750
|
-
}
|
|
751
|
-
|
|
752
|
-
if (type === 'number') {
|
|
753
|
-
code += `
|
|
754
|
-
var t = Number(obj[${sanitized}])
|
|
755
|
-
if (!isNaN(t)) {
|
|
756
|
-
${addComma}
|
|
757
|
-
json += ${asString} + ':' + t
|
|
758
|
-
`
|
|
759
|
-
} else if (type === 'integer') {
|
|
760
|
-
code += `
|
|
761
|
-
var rendered = false
|
|
762
|
-
var t = $asInteger(obj[${sanitized}])
|
|
763
|
-
if (!isNaN(t)) {
|
|
764
|
-
${addComma}
|
|
765
|
-
json += ${asString} + ':' + t
|
|
766
|
-
rendered = true
|
|
767
|
-
}
|
|
768
|
-
if (rendered) {
|
|
461
|
+
code += `
|
|
462
|
+
if (obj[${sanitized}] !== undefined) {
|
|
463
|
+
${addComma}
|
|
464
|
+
json += ${asString} + ':'
|
|
769
465
|
`
|
|
770
|
-
} else {
|
|
771
|
-
code += `
|
|
772
|
-
if (obj[${sanitized}] !== undefined) {
|
|
773
|
-
${addComma}
|
|
774
|
-
json += ${asString} + ':'
|
|
775
|
-
`
|
|
776
466
|
|
|
777
|
-
|
|
778
|
-
code += result.code
|
|
779
|
-
laterCode = result.laterCode
|
|
780
|
-
}
|
|
467
|
+
code += buildValue(locationPath + key, `obj[${JSON.stringify(key)}]`, mergeLocation(propertyLocation, { schema: schema.properties[key] }))
|
|
781
468
|
|
|
782
469
|
const defaultValue = schema.properties[key].default
|
|
783
470
|
if (defaultValue !== undefined) {
|
|
784
|
-
required = filterRequired(required, key)
|
|
785
471
|
code += `
|
|
786
472
|
} else {
|
|
787
473
|
${addComma}
|
|
788
474
|
json += ${asString} + ':' + ${JSON.stringify(JSON.stringify(defaultValue))}
|
|
789
475
|
`
|
|
790
|
-
} else if (required
|
|
791
|
-
required = filterRequired(required, key)
|
|
476
|
+
} else if (required.includes(key)) {
|
|
792
477
|
code += `
|
|
793
478
|
} else {
|
|
794
479
|
throw new Error('${sanitized} is required!')
|
|
@@ -798,79 +483,119 @@ function buildCode (location, code, laterCode, name) {
|
|
|
798
483
|
code += `
|
|
799
484
|
}
|
|
800
485
|
`
|
|
486
|
+
})
|
|
801
487
|
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
488
|
+
for (const requiredProperty of required) {
|
|
489
|
+
if (schema.properties && schema.properties[requiredProperty] !== undefined) continue
|
|
490
|
+
code += `if (obj['${requiredProperty}'] === undefined) throw new Error('"${requiredProperty}" is required!')\n`
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
return code
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
function mergeAllOfSchema (location, schema, mergedSchema) {
|
|
497
|
+
for (let allOfSchema of schema.allOf) {
|
|
498
|
+
if (allOfSchema.$ref) {
|
|
499
|
+
allOfSchema = refFinder(allOfSchema.$ref, mergeLocation(location, { schema: allOfSchema })).schema
|
|
806
500
|
}
|
|
807
|
-
})
|
|
808
501
|
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
502
|
+
let allOfSchemaType = allOfSchema.type
|
|
503
|
+
if (allOfSchemaType === undefined) {
|
|
504
|
+
allOfSchemaType = inferTypeByKeyword(allOfSchema)
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
if (allOfSchemaType !== undefined) {
|
|
508
|
+
if (
|
|
509
|
+
mergedSchema.type !== undefined &&
|
|
510
|
+
mergedSchema.type !== allOfSchemaType
|
|
511
|
+
) {
|
|
512
|
+
throw new Error('allOf schemas have different type values')
|
|
815
513
|
}
|
|
816
|
-
|
|
514
|
+
mergedSchema.type = allOfSchemaType
|
|
817
515
|
}
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
516
|
+
|
|
517
|
+
if (allOfSchema.format !== undefined) {
|
|
518
|
+
if (
|
|
519
|
+
mergedSchema.format !== undefined &&
|
|
520
|
+
mergedSchema.format !== allOfSchema.format
|
|
521
|
+
) {
|
|
522
|
+
throw new Error('allOf schemas have different format values')
|
|
821
523
|
}
|
|
822
|
-
|
|
823
|
-
|
|
524
|
+
mergedSchema.format = allOfSchema.format
|
|
525
|
+
}
|
|
824
526
|
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
527
|
+
if (allOfSchema.nullable !== undefined) {
|
|
528
|
+
if (
|
|
529
|
+
mergedSchema.nullable !== undefined &&
|
|
530
|
+
mergedSchema.nullable !== allOfSchema.nullable
|
|
531
|
+
) {
|
|
532
|
+
throw new Error('allOf schemas have different nullable values')
|
|
533
|
+
}
|
|
534
|
+
mergedSchema.nullable = allOfSchema.nullable
|
|
535
|
+
}
|
|
830
536
|
|
|
831
|
-
|
|
832
|
-
|
|
537
|
+
if (allOfSchema.properties !== undefined) {
|
|
538
|
+
if (mergedSchema.properties === undefined) {
|
|
539
|
+
mergedSchema.properties = {}
|
|
540
|
+
}
|
|
541
|
+
Object.assign(mergedSchema.properties, allOfSchema.properties)
|
|
542
|
+
}
|
|
833
543
|
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
}
|
|
544
|
+
if (allOfSchema.additionalProperties !== undefined) {
|
|
545
|
+
if (mergedSchema.additionalProperties === undefined) {
|
|
546
|
+
mergedSchema.additionalProperties = {}
|
|
547
|
+
}
|
|
548
|
+
Object.assign(mergedSchema.additionalProperties, allOfSchema.additionalProperties)
|
|
549
|
+
}
|
|
840
550
|
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
})
|
|
848
|
-
} else {
|
|
849
|
-
const builtCode = buildCode(location, code, laterCode, name)
|
|
551
|
+
if (allOfSchema.patternProperties !== undefined) {
|
|
552
|
+
if (mergedSchema.patternProperties === undefined) {
|
|
553
|
+
mergedSchema.patternProperties = {}
|
|
554
|
+
}
|
|
555
|
+
Object.assign(mergedSchema.patternProperties, allOfSchema.patternProperties)
|
|
556
|
+
}
|
|
850
557
|
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
558
|
+
if (allOfSchema.required !== undefined) {
|
|
559
|
+
if (mergedSchema.required === undefined) {
|
|
560
|
+
mergedSchema.required = []
|
|
561
|
+
}
|
|
562
|
+
mergedSchema.required.push(...allOfSchema.required)
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
if (allOfSchema.oneOf !== undefined) {
|
|
566
|
+
if (mergedSchema.oneOf === undefined) {
|
|
567
|
+
mergedSchema.oneOf = []
|
|
568
|
+
}
|
|
569
|
+
mergedSchema.oneOf.push(...allOfSchema.oneOf)
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
if (allOfSchema.anyOf !== undefined) {
|
|
573
|
+
if (mergedSchema.anyOf === undefined) {
|
|
574
|
+
mergedSchema.anyOf = []
|
|
575
|
+
}
|
|
576
|
+
mergedSchema.anyOf.push(...allOfSchema.anyOf)
|
|
577
|
+
}
|
|
854
578
|
|
|
855
|
-
|
|
579
|
+
if (allOfSchema.allOf !== undefined) {
|
|
580
|
+
mergeAllOfSchema(location, allOfSchema, mergedSchema)
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
delete mergedSchema.allOf
|
|
856
584
|
}
|
|
857
585
|
|
|
858
|
-
function buildInnerObject (location,
|
|
586
|
+
function buildInnerObject (location, locationPath) {
|
|
859
587
|
const schema = location.schema
|
|
860
|
-
|
|
588
|
+
let code = buildCode(location, locationPath)
|
|
861
589
|
if (schema.patternProperties) {
|
|
862
|
-
|
|
590
|
+
code += addPatternProperties(location)
|
|
863
591
|
} else if (schema.additionalProperties && !schema.patternProperties) {
|
|
864
|
-
|
|
592
|
+
code += addAdditionalProperties(location)
|
|
865
593
|
}
|
|
866
|
-
return
|
|
594
|
+
return code
|
|
867
595
|
}
|
|
868
596
|
|
|
869
|
-
function addIfThenElse (location,
|
|
597
|
+
function addIfThenElse (location, locationPath) {
|
|
870
598
|
let code = ''
|
|
871
|
-
let r
|
|
872
|
-
let laterCode = ''
|
|
873
|
-
let innerR
|
|
874
599
|
|
|
875
600
|
const schema = location.schema
|
|
876
601
|
const copy = merge({}, schema)
|
|
@@ -891,14 +616,10 @@ function addIfThenElse (location, name) {
|
|
|
891
616
|
if (valid) {
|
|
892
617
|
`
|
|
893
618
|
if (merged.if && merged.then) {
|
|
894
|
-
|
|
895
|
-
code += innerR.code
|
|
896
|
-
laterCode = innerR.laterCode
|
|
619
|
+
code += addIfThenElse(mergedLocation, locationPath + 'Then')
|
|
897
620
|
}
|
|
898
621
|
|
|
899
|
-
|
|
900
|
-
code += r.code
|
|
901
|
-
laterCode += r.laterCode
|
|
622
|
+
code += buildInnerObject(mergedLocation, locationPath + 'Then')
|
|
902
623
|
|
|
903
624
|
code += `
|
|
904
625
|
}
|
|
@@ -911,19 +632,15 @@ function addIfThenElse (location, name) {
|
|
|
911
632
|
`
|
|
912
633
|
|
|
913
634
|
if (merged.if && merged.then) {
|
|
914
|
-
|
|
915
|
-
code += innerR.code
|
|
916
|
-
laterCode += innerR.laterCode
|
|
635
|
+
code += addIfThenElse(mergedLocation, locationPath + 'Else')
|
|
917
636
|
}
|
|
918
637
|
|
|
919
|
-
|
|
920
|
-
code += r.code
|
|
921
|
-
laterCode += r.laterCode
|
|
638
|
+
code += buildInnerObject(mergedLocation, locationPath + 'Else')
|
|
922
639
|
|
|
923
640
|
code += `
|
|
924
641
|
}
|
|
925
642
|
`
|
|
926
|
-
return
|
|
643
|
+
return code
|
|
927
644
|
}
|
|
928
645
|
|
|
929
646
|
function toJSON (variableName) {
|
|
@@ -933,74 +650,63 @@ function toJSON (variableName) {
|
|
|
933
650
|
`
|
|
934
651
|
}
|
|
935
652
|
|
|
936
|
-
function buildObject (location,
|
|
653
|
+
function buildObject (location, locationPath) {
|
|
937
654
|
const schema = location.schema
|
|
938
655
|
if (schema.$id !== undefined) {
|
|
939
656
|
schemaReferenceMap.set(schema.$id, schema)
|
|
940
657
|
}
|
|
941
|
-
|
|
942
|
-
|
|
658
|
+
|
|
659
|
+
if (objectReferenceSerializersMap.has(schema)) {
|
|
660
|
+
return objectReferenceSerializersMap.get(schema)
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
const functionName = generateFuncName()
|
|
664
|
+
objectReferenceSerializersMap.set(schema, functionName)
|
|
665
|
+
|
|
666
|
+
let functionCode = `
|
|
667
|
+
function ${functionName} (input) {
|
|
668
|
+
// ${locationPath}
|
|
943
669
|
`
|
|
944
670
|
if (schema.nullable) {
|
|
945
|
-
|
|
946
|
-
if(input === null) {
|
|
671
|
+
functionCode += `
|
|
672
|
+
if (input === null) {
|
|
947
673
|
return 'null';
|
|
948
674
|
}
|
|
949
675
|
`
|
|
950
676
|
}
|
|
951
677
|
|
|
952
|
-
|
|
953
|
-
code += `
|
|
954
|
-
return ${objectReferenceSerializersMap.get(schema)}(input)
|
|
955
|
-
}
|
|
956
|
-
`
|
|
957
|
-
return code
|
|
958
|
-
}
|
|
959
|
-
objectReferenceSerializersMap.set(schema, name)
|
|
960
|
-
|
|
961
|
-
code += `
|
|
678
|
+
functionCode += `
|
|
962
679
|
var obj = ${toJSON('input')}
|
|
963
680
|
var json = '{'
|
|
964
681
|
var addComma = false
|
|
965
682
|
`
|
|
966
683
|
|
|
967
|
-
let
|
|
684
|
+
let rCode
|
|
968
685
|
if (schema.if && schema.then) {
|
|
969
|
-
|
|
686
|
+
functionCode += `
|
|
970
687
|
var valid
|
|
971
688
|
`
|
|
972
|
-
|
|
689
|
+
rCode = addIfThenElse(location, locationPath)
|
|
973
690
|
} else {
|
|
974
|
-
|
|
691
|
+
rCode = buildInnerObject(location, locationPath)
|
|
975
692
|
}
|
|
976
693
|
|
|
977
694
|
// Removes the comma if is the last element of the string (in case there are not properties)
|
|
978
|
-
|
|
695
|
+
functionCode += `${rCode}
|
|
979
696
|
json += '}'
|
|
980
697
|
return json
|
|
981
698
|
}
|
|
982
|
-
${r.laterCode}
|
|
983
699
|
`
|
|
984
700
|
|
|
985
|
-
|
|
701
|
+
contextFunctions.push(functionCode)
|
|
702
|
+
return functionName
|
|
986
703
|
}
|
|
987
704
|
|
|
988
|
-
function buildArray (location,
|
|
705
|
+
function buildArray (location, locationPath) {
|
|
989
706
|
let schema = location.schema
|
|
990
707
|
if (schema.$id !== undefined) {
|
|
991
708
|
schemaReferenceMap.set(schema.$id, schema)
|
|
992
709
|
}
|
|
993
|
-
code += `
|
|
994
|
-
function ${name} (obj) {
|
|
995
|
-
`
|
|
996
|
-
if (schema.nullable) {
|
|
997
|
-
code += `
|
|
998
|
-
if(obj === null) {
|
|
999
|
-
return 'null';
|
|
1000
|
-
}
|
|
1001
|
-
`
|
|
1002
|
-
}
|
|
1003
|
-
const laterCode = ''
|
|
1004
710
|
|
|
1005
711
|
// default to any items type
|
|
1006
712
|
if (!schema.items) {
|
|
@@ -1016,100 +722,105 @@ function buildArray (location, code, name, key = null) {
|
|
|
1016
722
|
|
|
1017
723
|
location = refFinder(schema.items.$ref, location)
|
|
1018
724
|
schema.items = location.schema
|
|
725
|
+
}
|
|
1019
726
|
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
return ${arrayItemsReferenceSerializersMap.get(schema.items)}(obj)
|
|
1023
|
-
}
|
|
1024
|
-
`
|
|
1025
|
-
return code
|
|
1026
|
-
}
|
|
1027
|
-
arrayItemsReferenceSerializersMap.set(schema.items, name)
|
|
727
|
+
if (arrayItemsReferenceSerializersMap.has(schema.items)) {
|
|
728
|
+
return arrayItemsReferenceSerializersMap.get(schema.items)
|
|
1028
729
|
}
|
|
1029
730
|
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
if (Array.isArray(schema.items)) {
|
|
1033
|
-
result = schema.items.reduce((res, item, i) => {
|
|
1034
|
-
const tmpRes = nested(laterCode, name, accessor, mergeLocation(location, { schema: item }), i, true)
|
|
1035
|
-
const condition = `i === ${i} && ${buildArrayTypeCondition(item.type, accessor)}`
|
|
1036
|
-
return {
|
|
1037
|
-
code: `${res.code}
|
|
1038
|
-
${i > 0 ? 'else' : ''} if (${condition}) {
|
|
1039
|
-
${tmpRes.code}
|
|
1040
|
-
}`,
|
|
1041
|
-
laterCode: `${res.laterCode}
|
|
1042
|
-
${tmpRes.laterCode}`
|
|
1043
|
-
}
|
|
1044
|
-
}, result)
|
|
731
|
+
const functionName = generateFuncName()
|
|
732
|
+
arrayItemsReferenceSerializersMap.set(schema.items, functionName)
|
|
1045
733
|
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
${tmpRes.code}
|
|
1051
|
-
}
|
|
1052
|
-
`
|
|
1053
|
-
}
|
|
734
|
+
let functionCode = `
|
|
735
|
+
function ${functionName} (obj) {
|
|
736
|
+
// ${locationPath}
|
|
737
|
+
`
|
|
1054
738
|
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
739
|
+
if (schema.nullable) {
|
|
740
|
+
functionCode += `
|
|
741
|
+
if (obj === null) {
|
|
742
|
+
return 'null';
|
|
743
|
+
}
|
|
1059
744
|
`
|
|
1060
|
-
} else {
|
|
1061
|
-
result = nested(laterCode, name, accessor, mergeLocation(location, { schema: schema.items }), undefined, true)
|
|
1062
745
|
}
|
|
1063
746
|
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
throw new TypeError(\`Property '${key}' should be of type array, received '$\{obj}' instead.\`)
|
|
747
|
+
functionCode += `
|
|
748
|
+
if (!Array.isArray(obj)) {
|
|
749
|
+
throw new TypeError(\`The value '$\{obj}' does not match schema definition.\`)
|
|
1068
750
|
}
|
|
751
|
+
const arrayLength = obj.length
|
|
752
|
+
`
|
|
753
|
+
|
|
754
|
+
if (!schema.additionalItems) {
|
|
755
|
+
functionCode += `
|
|
756
|
+
if (arrayLength > ${schema.items.length}) {
|
|
757
|
+
throw new Error(\`Item at ${schema.items.length} does not match schema definition.\`)
|
|
758
|
+
}
|
|
1069
759
|
`
|
|
1070
760
|
}
|
|
1071
761
|
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
762
|
+
if (largeArrayMechanism !== 'default') {
|
|
763
|
+
if (largeArrayMechanism === 'json-stringify') {
|
|
764
|
+
functionCode += `if (arrayLength && arrayLength >= ${largeArraySize}) return JSON.stringify(obj)\n`
|
|
765
|
+
} else {
|
|
766
|
+
throw new Error(`Unsupported large array mechanism ${largeArrayMechanism}`)
|
|
1077
767
|
}
|
|
768
|
+
}
|
|
1078
769
|
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
${result.code}
|
|
1083
|
-
jsonOutput += json
|
|
770
|
+
functionCode += `
|
|
771
|
+
let jsonOutput = ''
|
|
772
|
+
`
|
|
1084
773
|
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
774
|
+
const accessor = '[i]'
|
|
775
|
+
if (Array.isArray(schema.items)) {
|
|
776
|
+
for (let i = 0; i < schema.items.length; i++) {
|
|
777
|
+
const item = schema.items[i]
|
|
778
|
+
const tmpRes = buildValue(locationPath + accessor + i, `obj[${i}]`, mergeLocation(location, { schema: item }))
|
|
779
|
+
functionCode += `
|
|
780
|
+
if (${i} < arrayLength) {
|
|
781
|
+
if (${buildArrayTypeCondition(item.type, `[${i}]`)}) {
|
|
782
|
+
let json = ''
|
|
783
|
+
${tmpRes}
|
|
784
|
+
jsonOutput += json
|
|
785
|
+
if (${i} < arrayLength - 1) {
|
|
786
|
+
jsonOutput += ','
|
|
787
|
+
}
|
|
788
|
+
} else {
|
|
789
|
+
throw new Error(\`Item at ${i} does not match schema definition.\`)
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
`
|
|
1088
793
|
}
|
|
1089
|
-
return \`[\${jsonOutput}]\`
|
|
1090
|
-
}`
|
|
1091
|
-
|
|
1092
|
-
switch (largeArrayMechanism) {
|
|
1093
|
-
case 'default':
|
|
1094
|
-
code += `
|
|
1095
|
-
return \`[\${obj.map(${result.mapFnName}).join(',')}]\``
|
|
1096
|
-
break
|
|
1097
794
|
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
795
|
+
if (schema.additionalItems) {
|
|
796
|
+
functionCode += `
|
|
797
|
+
for (let i = ${schema.items.length}; i < arrayLength; i++) {
|
|
798
|
+
let json = JSON.stringify(obj[i])
|
|
799
|
+
jsonOutput += json
|
|
800
|
+
if (i < arrayLength - 1) {
|
|
801
|
+
jsonOutput += ','
|
|
802
|
+
}
|
|
803
|
+
}`
|
|
804
|
+
}
|
|
805
|
+
} else {
|
|
806
|
+
const code = buildValue(locationPath + accessor, 'obj[i]', mergeLocation(location, { schema: schema.items }))
|
|
807
|
+
functionCode += `
|
|
808
|
+
for (let i = 0; i < arrayLength; i++) {
|
|
809
|
+
let json = ''
|
|
810
|
+
${code}
|
|
811
|
+
jsonOutput += json
|
|
812
|
+
if (i < arrayLength - 1) {
|
|
813
|
+
jsonOutput += ','
|
|
814
|
+
}
|
|
815
|
+
}`
|
|
1105
816
|
}
|
|
1106
817
|
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
`
|
|
818
|
+
functionCode += `
|
|
819
|
+
return \`[\${jsonOutput}]\`
|
|
820
|
+
}`
|
|
1111
821
|
|
|
1112
|
-
|
|
822
|
+
contextFunctions.push(functionCode)
|
|
823
|
+
return functionName
|
|
1113
824
|
}
|
|
1114
825
|
|
|
1115
826
|
function buildArrayTypeCondition (type, accessor) {
|
|
@@ -1173,26 +884,12 @@ function dereferenceOfRefs (location, type) {
|
|
|
1173
884
|
return locations
|
|
1174
885
|
}
|
|
1175
886
|
|
|
1176
|
-
let
|
|
1177
|
-
function
|
|
1178
|
-
|
|
1179
|
-
let rep = str.replace(/[^a-zA-Z0-9$_]/g, '')
|
|
1180
|
-
|
|
1181
|
-
if (rep.length === 0) {
|
|
1182
|
-
return 'anan' + strNameCounter++
|
|
1183
|
-
} else if (rep !== str) {
|
|
1184
|
-
rep += strNameCounter++
|
|
1185
|
-
}
|
|
1186
|
-
|
|
1187
|
-
return rep
|
|
887
|
+
let genFuncNameCounter = 0
|
|
888
|
+
function generateFuncName () {
|
|
889
|
+
return 'anonymous' + genFuncNameCounter++
|
|
1188
890
|
}
|
|
1189
891
|
|
|
1190
|
-
function
|
|
1191
|
-
let code = ''
|
|
1192
|
-
let funcName
|
|
1193
|
-
|
|
1194
|
-
subKey = subKey || ''
|
|
1195
|
-
|
|
892
|
+
function buildValue (locationPath, input, location) {
|
|
1196
893
|
let schema = location.schema
|
|
1197
894
|
|
|
1198
895
|
if (schema.$ref) {
|
|
@@ -1206,121 +903,96 @@ function nested (laterCode, name, key, location, subKey, isArray) {
|
|
|
1206
903
|
}
|
|
1207
904
|
}
|
|
1208
905
|
|
|
906
|
+
if (schema.allOf) {
|
|
907
|
+
const mergedSchema = clone(schema)
|
|
908
|
+
mergeAllOfSchema(location, schema, mergedSchema)
|
|
909
|
+
schema = mergedSchema
|
|
910
|
+
location.schema = mergedSchema
|
|
911
|
+
}
|
|
912
|
+
|
|
1209
913
|
const type = schema.type
|
|
1210
914
|
const nullable = schema.nullable === true
|
|
1211
915
|
|
|
1212
|
-
|
|
916
|
+
let code = ''
|
|
917
|
+
let funcName
|
|
1213
918
|
|
|
1214
919
|
switch (type) {
|
|
1215
920
|
case 'null':
|
|
1216
|
-
funcName = '$asNull'
|
|
1217
921
|
code += `
|
|
1218
|
-
json +=
|
|
922
|
+
json += serializer.asNull()
|
|
1219
923
|
`
|
|
1220
924
|
break
|
|
1221
925
|
case 'string': {
|
|
1222
|
-
funcName =
|
|
1223
|
-
|
|
1224
|
-
code += nullable ? `json += obj${accessor} === null ? null : ${stringSerializer}(obj${accessor})` : `json += ${stringSerializer}(obj${accessor})`
|
|
926
|
+
funcName = getStringSerializer(schema.format, nullable)
|
|
927
|
+
code += `json += ${funcName}(${input})`
|
|
1225
928
|
break
|
|
1226
929
|
}
|
|
1227
930
|
case 'integer':
|
|
1228
|
-
funcName = '
|
|
1229
|
-
code +=
|
|
931
|
+
funcName = nullable ? 'serializer.asIntegerNullable.bind(serializer)' : 'serializer.asInteger.bind(serializer)'
|
|
932
|
+
code += `json += ${funcName}(${input})`
|
|
1230
933
|
break
|
|
1231
934
|
case 'number':
|
|
1232
|
-
funcName = '
|
|
1233
|
-
code +=
|
|
935
|
+
funcName = nullable ? 'serializer.asNumberNullable.bind(serializer)' : 'serializer.asNumber.bind(serializer)'
|
|
936
|
+
code += `json += ${funcName}(${input})`
|
|
1234
937
|
break
|
|
1235
938
|
case 'boolean':
|
|
1236
|
-
funcName = '
|
|
1237
|
-
code +=
|
|
939
|
+
funcName = nullable ? 'serializer.asBooleanNullable.bind(serializer)' : 'serializer.asBoolean.bind(serializer)'
|
|
940
|
+
code += `json += ${funcName}(${input})`
|
|
1238
941
|
break
|
|
1239
942
|
case 'object':
|
|
1240
|
-
funcName =
|
|
1241
|
-
|
|
1242
|
-
code += `
|
|
1243
|
-
json += ${funcName}(obj${accessor})
|
|
1244
|
-
`
|
|
943
|
+
funcName = buildObject(location, locationPath)
|
|
944
|
+
code += `json += ${funcName}(${input})`
|
|
1245
945
|
break
|
|
1246
946
|
case 'array':
|
|
1247
|
-
funcName =
|
|
1248
|
-
|
|
1249
|
-
code += `
|
|
1250
|
-
json += ${funcName}(obj${accessor})
|
|
1251
|
-
`
|
|
947
|
+
funcName = buildArray(location, locationPath)
|
|
948
|
+
code += `json += ${funcName}(${input})`
|
|
1252
949
|
break
|
|
1253
950
|
case undefined:
|
|
1254
|
-
|
|
1255
|
-
if ('anyOf' in schema) {
|
|
951
|
+
if (schema.anyOf || schema.oneOf) {
|
|
1256
952
|
// beware: dereferenceOfRefs has side effects and changes schema.anyOf
|
|
1257
|
-
const
|
|
1258
|
-
|
|
1259
|
-
const nestedResult =
|
|
1260
|
-
// We need a test serializer as the String serializer will not work with
|
|
1261
|
-
// date/time ajv validations
|
|
1262
|
-
// see: https://github.com/fastify/fast-json-stringify/issues/325
|
|
1263
|
-
const testSerializer = getTestSerializer(location.schema.format)
|
|
1264
|
-
const testValue = testSerializer !== undefined ? `${testSerializer}(obj${accessor}, true)` : `obj${accessor}`
|
|
1265
|
-
|
|
953
|
+
const locations = dereferenceOfRefs(location, schema.anyOf ? 'anyOf' : 'oneOf')
|
|
954
|
+
locations.forEach((location, index) => {
|
|
955
|
+
const nestedResult = buildValue(locationPath + 'i' + index, input, location)
|
|
1266
956
|
// Since we are only passing the relevant schema to ajv.validate, it needs to be full dereferenced
|
|
1267
957
|
// otherwise any $ref pointing to an external schema would result in an error.
|
|
1268
958
|
// Full dereference of the schema happens as side effect of two functions:
|
|
1269
959
|
// 1. `dereferenceOfRefs` loops through the `schema.anyOf`` array and replaces any top level reference
|
|
1270
960
|
// with the actual schema
|
|
1271
|
-
// 2. `
|
|
961
|
+
// 2. `buildValue`, through `buildCode`, replaces any reference in object properties with the actual schema
|
|
1272
962
|
// (see https://github.com/fastify/fast-json-stringify/blob/6da3b3e8ac24b1ca5578223adedb4083b7adf8db/index.js#L631)
|
|
1273
963
|
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
${nestedResult.code}
|
|
1280
|
-
`
|
|
1281
|
-
laterCode = nestedResult.laterCode
|
|
1282
|
-
})
|
|
1283
|
-
code += `
|
|
1284
|
-
else json+= null
|
|
1285
|
-
`
|
|
1286
|
-
} else if ('oneOf' in schema) {
|
|
1287
|
-
// beware: dereferenceOfRefs has side effects and changes schema.oneOf
|
|
1288
|
-
const oneOfLocations = dereferenceOfRefs(location, 'oneOf')
|
|
1289
|
-
oneOfLocations.forEach((location, index) => {
|
|
1290
|
-
const nestedResult = nested(laterCode, name, key, location, subKey !== '' ? subKey : 'i' + index, isArray)
|
|
1291
|
-
const testSerializer = getTestSerializer(location.schema.format)
|
|
1292
|
-
const testValue = testSerializer !== undefined ? `${testSerializer}(obj${accessor}, true)` : `obj${accessor}`
|
|
1293
|
-
// see comment on anyOf about dereferencing the schema before calling ajv.validate
|
|
964
|
+
// Ajv does not support js date format. In order to properly validate objects containing a date,
|
|
965
|
+
// it needs to replace all occurrences of the string date format with a custom keyword fjs_date_type.
|
|
966
|
+
// (see https://github.com/fastify/fast-json-stringify/pull/441)
|
|
967
|
+
const extendedSchema = clone(location.schema)
|
|
968
|
+
extendDateTimeType(extendedSchema)
|
|
1294
969
|
|
|
1295
970
|
const schemaKey = location.schema.$id || randomUUID()
|
|
1296
|
-
ajvInstance.addSchema(
|
|
971
|
+
ajvInstance.addSchema(extendedSchema, schemaKey)
|
|
1297
972
|
|
|
1298
973
|
code += `
|
|
1299
|
-
${index === 0 ? 'if' : 'else if'}(ajv.validate("${schemaKey}", ${
|
|
1300
|
-
${nestedResult
|
|
974
|
+
${index === 0 ? 'if' : 'else if'}(ajv.validate("${schemaKey}", ${input}))
|
|
975
|
+
${nestedResult}
|
|
1301
976
|
`
|
|
1302
|
-
laterCode = nestedResult.laterCode
|
|
1303
977
|
})
|
|
1304
978
|
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
`
|
|
1309
|
-
}
|
|
979
|
+
code += `
|
|
980
|
+
else throw new Error(\`The value $\{JSON.stringify(${input})} does not match schema definition.\`)
|
|
981
|
+
`
|
|
1310
982
|
} else if (isEmpty(schema)) {
|
|
1311
983
|
code += `
|
|
1312
|
-
json += JSON.stringify(
|
|
984
|
+
json += JSON.stringify(${input})
|
|
1313
985
|
`
|
|
1314
986
|
} else if ('const' in schema) {
|
|
1315
987
|
code += `
|
|
1316
|
-
if(ajv.validate(${JSON.stringify(schema)},
|
|
988
|
+
if(ajv.validate(${JSON.stringify(schema)}, ${input}))
|
|
1317
989
|
json += '${JSON.stringify(schema.const)}'
|
|
1318
990
|
else
|
|
1319
|
-
throw new Error(\`Item $\{JSON.stringify(
|
|
991
|
+
throw new Error(\`Item $\{JSON.stringify(${input})} does not match schema definition.\`)
|
|
1320
992
|
`
|
|
1321
993
|
} else if (schema.type === undefined) {
|
|
1322
994
|
code += `
|
|
1323
|
-
json += JSON.stringify(
|
|
995
|
+
json += JSON.stringify(${input})
|
|
1324
996
|
`
|
|
1325
997
|
} else {
|
|
1326
998
|
throw new Error(`${schema.type} unsupported`)
|
|
@@ -1328,70 +1000,79 @@ function nested (laterCode, name, key, location, subKey, isArray) {
|
|
|
1328
1000
|
break
|
|
1329
1001
|
default:
|
|
1330
1002
|
if (Array.isArray(type)) {
|
|
1331
|
-
|
|
1332
|
-
const
|
|
1003
|
+
let sortedTypes = type
|
|
1004
|
+
const nullable = schema.nullable === true || type.includes('null')
|
|
1005
|
+
|
|
1006
|
+
if (nullable) {
|
|
1007
|
+
sortedTypes = sortedTypes.filter(type => type !== 'null')
|
|
1008
|
+
code += `
|
|
1009
|
+
if (${input} === null) {
|
|
1010
|
+
json += null
|
|
1011
|
+
} else {`
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1333
1014
|
sortedTypes.forEach((type, index) => {
|
|
1334
1015
|
const statement = index === 0 ? 'if' : 'else if'
|
|
1335
1016
|
const tempSchema = Object.assign({}, schema, { type })
|
|
1336
|
-
const nestedResult =
|
|
1017
|
+
const nestedResult = buildValue(locationPath, input, mergeLocation(location, { schema: tempSchema }))
|
|
1337
1018
|
switch (type) {
|
|
1338
1019
|
case 'string': {
|
|
1339
1020
|
code += `
|
|
1340
|
-
${statement}(
|
|
1341
|
-
${nestedResult
|
|
1342
|
-
`
|
|
1343
|
-
break
|
|
1344
|
-
}
|
|
1345
|
-
case 'null': {
|
|
1346
|
-
code += `
|
|
1347
|
-
${statement}(obj${accessor} == null)
|
|
1348
|
-
${nestedResult.code}
|
|
1021
|
+
${statement}(${input} === null || typeof ${input} === "${type}" || ${input} instanceof Date || ${input} instanceof RegExp || (typeof ${input} === "object" && Object.hasOwnProperty.call(${input}, "toString")))
|
|
1022
|
+
${nestedResult}
|
|
1349
1023
|
`
|
|
1350
1024
|
break
|
|
1351
1025
|
}
|
|
1352
1026
|
case 'array': {
|
|
1353
1027
|
code += `
|
|
1354
|
-
${statement}(Array.isArray(
|
|
1355
|
-
${nestedResult
|
|
1028
|
+
${statement}(Array.isArray(${input}))
|
|
1029
|
+
${nestedResult}
|
|
1356
1030
|
`
|
|
1357
1031
|
break
|
|
1358
1032
|
}
|
|
1359
1033
|
case 'integer': {
|
|
1360
1034
|
code += `
|
|
1361
|
-
${statement}(Number.isInteger(
|
|
1362
|
-
${nestedResult
|
|
1363
|
-
`
|
|
1364
|
-
break
|
|
1365
|
-
}
|
|
1366
|
-
case 'number': {
|
|
1367
|
-
code += `
|
|
1368
|
-
${statement}(isNaN(obj${accessor}) === false)
|
|
1369
|
-
${nestedResult.code}
|
|
1035
|
+
${statement}(Number.isInteger(${input}) || ${input} === null)
|
|
1036
|
+
${nestedResult}
|
|
1370
1037
|
`
|
|
1371
1038
|
break
|
|
1372
1039
|
}
|
|
1373
1040
|
default: {
|
|
1374
1041
|
code += `
|
|
1375
|
-
${statement}(typeof
|
|
1376
|
-
${nestedResult
|
|
1042
|
+
${statement}(typeof ${input} === "${type}" || ${input} === null)
|
|
1043
|
+
${nestedResult}
|
|
1377
1044
|
`
|
|
1378
1045
|
break
|
|
1379
1046
|
}
|
|
1380
1047
|
}
|
|
1381
|
-
laterCode = nestedResult.laterCode
|
|
1382
1048
|
})
|
|
1383
1049
|
code += `
|
|
1384
|
-
else
|
|
1050
|
+
else throw new Error(\`The value $\{JSON.stringify(${input})} does not match schema definition.\`)
|
|
1385
1051
|
`
|
|
1052
|
+
|
|
1053
|
+
if (nullable) {
|
|
1054
|
+
code += `
|
|
1055
|
+
}
|
|
1056
|
+
`
|
|
1057
|
+
}
|
|
1386
1058
|
} else {
|
|
1387
1059
|
throw new Error(`${type} unsupported`)
|
|
1388
1060
|
}
|
|
1389
1061
|
}
|
|
1390
1062
|
|
|
1391
|
-
return
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1063
|
+
return code
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
function extendDateTimeType (schema) {
|
|
1067
|
+
if (schema.type === 'string' && ['date-time', 'date', 'time'].includes(schema.format)) {
|
|
1068
|
+
schema.fjs_date_type = schema.format
|
|
1069
|
+
delete schema.type
|
|
1070
|
+
delete schema.format
|
|
1071
|
+
}
|
|
1072
|
+
for (const property in schema) {
|
|
1073
|
+
if (typeof schema[property] === 'object') {
|
|
1074
|
+
extendDateTimeType(schema[property])
|
|
1075
|
+
}
|
|
1395
1076
|
}
|
|
1396
1077
|
}
|
|
1397
1078
|
|
|
@@ -1410,7 +1091,8 @@ module.exports = build
|
|
|
1410
1091
|
module.exports.validLargeArrayMechanisms = validLargeArrayMechanisms
|
|
1411
1092
|
|
|
1412
1093
|
module.exports.restore = function ({ code, ajv }) {
|
|
1094
|
+
const serializer = new Serializer()
|
|
1413
1095
|
// eslint-disable-next-line
|
|
1414
|
-
return (Function.apply(null, ['ajv', code])
|
|
1415
|
-
.apply(null, [ajv]))
|
|
1096
|
+
return (Function.apply(null, ['ajv', 'serializer', code])
|
|
1097
|
+
.apply(null, [ajv, serializer]))
|
|
1416
1098
|
}
|