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