fast-json-stringify 3.0.3 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/benchmark.yml +9 -4
- package/.github/workflows/ci.yml +3 -33
- package/README.md +65 -11
- package/benchmark/bench-cmp-branch.js +114 -0
- package/{bench.js → benchmark/bench-cmp-lib.js} +70 -8
- package/benchmark/bench-thread.js +21 -0
- package/benchmark/bench.js +251 -0
- package/example.js +1 -4
- package/index.d.ts +4 -0
- package/index.js +540 -585
- package/package.json +13 -6
- 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 +126 -24
- package/test/basic.test.js +8 -8
- package/test/date.test.js +62 -35
- package/test/debug-mode.test.js +1 -1
- 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/typesArray.test.js +40 -3
package/index.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
/* eslint no-prototype-builtins: 0 */
|
|
4
4
|
|
|
5
5
|
const Ajv = require('ajv')
|
|
6
|
+
const fastUri = require('fast-uri')
|
|
6
7
|
const ajvFormats = require('ajv-formats')
|
|
7
8
|
const merge = require('deepmerge')
|
|
8
9
|
const clone = require('rfdc')({ proto: true })
|
|
@@ -10,7 +11,14 @@ const fjsCloned = Symbol('fast-json-stringify.cloned')
|
|
|
10
11
|
const { randomUUID } = require('crypto')
|
|
11
12
|
|
|
12
13
|
const validate = require('./schema-validator')
|
|
14
|
+
|
|
15
|
+
let largeArraySize = 2e4
|
|
13
16
|
let stringSimilarity = null
|
|
17
|
+
let largeArrayMechanism = 'default'
|
|
18
|
+
const validLargeArrayMechanisms = [
|
|
19
|
+
'default',
|
|
20
|
+
'json-stringify'
|
|
21
|
+
]
|
|
14
22
|
|
|
15
23
|
const addComma = `
|
|
16
24
|
if (addComma) {
|
|
@@ -44,17 +52,210 @@ function mergeLocation (source, dest) {
|
|
|
44
52
|
|
|
45
53
|
const arrayItemsReferenceSerializersMap = new Map()
|
|
46
54
|
const objectReferenceSerializersMap = new Map()
|
|
55
|
+
const schemaReferenceMap = new Map()
|
|
56
|
+
|
|
47
57
|
let ajvInstance = null
|
|
48
58
|
|
|
59
|
+
class Serializer {
|
|
60
|
+
constructor (options = {}) {
|
|
61
|
+
switch (options.rounding) {
|
|
62
|
+
case 'floor':
|
|
63
|
+
this.parseInteger = Math.floor
|
|
64
|
+
break
|
|
65
|
+
case 'ceil':
|
|
66
|
+
this.parseInteger = Math.ceil
|
|
67
|
+
break
|
|
68
|
+
case 'round':
|
|
69
|
+
this.parseInteger = Math.round
|
|
70
|
+
break
|
|
71
|
+
default:
|
|
72
|
+
this.parseInteger = Math.trunc
|
|
73
|
+
break
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
asAny (i) {
|
|
78
|
+
return JSON.stringify(i)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
asNull () {
|
|
82
|
+
return 'null'
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
asInteger (i) {
|
|
86
|
+
if (typeof i === 'bigint') {
|
|
87
|
+
return i.toString()
|
|
88
|
+
} else if (Number.isInteger(i)) {
|
|
89
|
+
return '' + i
|
|
90
|
+
} else {
|
|
91
|
+
/* eslint no-undef: "off" */
|
|
92
|
+
const integer = this.parseInteger(i)
|
|
93
|
+
if (Number.isNaN(integer)) {
|
|
94
|
+
throw new Error(`The value "${i}" cannot be converted to an integer.`)
|
|
95
|
+
} else {
|
|
96
|
+
return '' + integer
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
asIntegerNullable (i) {
|
|
102
|
+
return i === null ? 'null' : this.asInteger(i)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
asNumber (i) {
|
|
106
|
+
const num = Number(i)
|
|
107
|
+
if (Number.isNaN(num)) {
|
|
108
|
+
throw new Error(`The value "${i}" cannot be converted to a number.`)
|
|
109
|
+
} else {
|
|
110
|
+
return '' + num
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
asNumberNullable (i) {
|
|
115
|
+
return i === null ? 'null' : this.asNumber(i)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
asBoolean (bool) {
|
|
119
|
+
return bool && 'true' || 'false' // eslint-disable-line
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
asBooleanNullable (bool) {
|
|
123
|
+
return bool === null ? 'null' : this.asBoolean(bool)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
asDatetime (date, skipQuotes) {
|
|
127
|
+
const quotes = skipQuotes === true ? '' : '"'
|
|
128
|
+
if (date instanceof Date) {
|
|
129
|
+
return quotes + date.toISOString() + quotes
|
|
130
|
+
}
|
|
131
|
+
return this.asString(date, skipQuotes)
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
asDatetimeNullable (date, skipQuotes) {
|
|
135
|
+
return date === null ? 'null' : this.asDatetime(date, skipQuotes)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
asDate (date, skipQuotes) {
|
|
139
|
+
const quotes = skipQuotes === true ? '' : '"'
|
|
140
|
+
if (date instanceof Date) {
|
|
141
|
+
return quotes + new Date(date.getTime() - (date.getTimezoneOffset() * 60000)).toISOString().slice(0, 10) + quotes
|
|
142
|
+
}
|
|
143
|
+
return this.asString(date, skipQuotes)
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
asDateNullable (date, skipQuotes) {
|
|
147
|
+
return date === null ? 'null' : this.asDate(date, skipQuotes)
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
asTime (date, skipQuotes) {
|
|
151
|
+
const quotes = skipQuotes === true ? '' : '"'
|
|
152
|
+
if (date instanceof Date) {
|
|
153
|
+
return quotes + new Date(date.getTime() - (date.getTimezoneOffset() * 60000)).toISOString().slice(11, 19) + quotes
|
|
154
|
+
}
|
|
155
|
+
return this.asString(date, skipQuotes)
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
asTimeNullable (date, skipQuotes) {
|
|
159
|
+
return date === null ? 'null' : this.asTime(date, skipQuotes)
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
asString (str, skipQuotes) {
|
|
163
|
+
const quotes = skipQuotes === true ? '' : '"'
|
|
164
|
+
if (str instanceof Date) {
|
|
165
|
+
return quotes + str.toISOString() + quotes
|
|
166
|
+
} else if (str === null) {
|
|
167
|
+
return quotes + quotes
|
|
168
|
+
} else if (str instanceof RegExp) {
|
|
169
|
+
str = str.source
|
|
170
|
+
} else if (typeof str !== 'string') {
|
|
171
|
+
str = str.toString()
|
|
172
|
+
}
|
|
173
|
+
// If we skipQuotes it means that we are using it as test
|
|
174
|
+
// no need to test the string length for the render
|
|
175
|
+
if (skipQuotes) {
|
|
176
|
+
return str
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (str.length < 42) {
|
|
180
|
+
return this.asStringSmall(str)
|
|
181
|
+
} else {
|
|
182
|
+
return JSON.stringify(str)
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
asStringNullable (str) {
|
|
187
|
+
return str === null ? 'null' : this.asString(str)
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// magically escape strings for json
|
|
191
|
+
// relying on their charCodeAt
|
|
192
|
+
// everything below 32 needs JSON.stringify()
|
|
193
|
+
// every string that contain surrogate needs JSON.stringify()
|
|
194
|
+
// 34 and 92 happens all the time, so we
|
|
195
|
+
// have a fast case for them
|
|
196
|
+
asStringSmall (str) {
|
|
197
|
+
const l = str.length
|
|
198
|
+
let result = ''
|
|
199
|
+
let last = 0
|
|
200
|
+
let found = false
|
|
201
|
+
let surrogateFound = false
|
|
202
|
+
let point = 255
|
|
203
|
+
// eslint-disable-next-line
|
|
204
|
+
for (var i = 0; i < l && point >= 32; i++) {
|
|
205
|
+
point = str.charCodeAt(i)
|
|
206
|
+
if (point >= 0xD800 && point <= 0xDFFF) {
|
|
207
|
+
// The current character is a surrogate.
|
|
208
|
+
surrogateFound = true
|
|
209
|
+
}
|
|
210
|
+
if (point === 34 || point === 92) {
|
|
211
|
+
result += str.slice(last, i) + '\\'
|
|
212
|
+
last = i
|
|
213
|
+
found = true
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (!found) {
|
|
218
|
+
result = str
|
|
219
|
+
} else {
|
|
220
|
+
result += str.slice(last)
|
|
221
|
+
}
|
|
222
|
+
return ((point < 32) || (surrogateFound === true)) ? JSON.stringify(str) : '"' + result + '"'
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
49
226
|
function build (schema, options) {
|
|
50
227
|
arrayItemsReferenceSerializersMap.clear()
|
|
51
228
|
objectReferenceSerializersMap.clear()
|
|
229
|
+
schemaReferenceMap.clear()
|
|
52
230
|
|
|
53
231
|
options = options || {}
|
|
54
232
|
|
|
55
|
-
ajvInstance = new Ajv({ ...options.ajv, strictSchema: false })
|
|
233
|
+
ajvInstance = new Ajv({ ...options.ajv, strictSchema: false, uriResolver: fastUri })
|
|
56
234
|
ajvFormats(ajvInstance)
|
|
57
235
|
|
|
236
|
+
const validateDateTimeFormat = ajvFormats.get('date-time').validate
|
|
237
|
+
const validateDateFormat = ajvFormats.get('date').validate
|
|
238
|
+
const validateTimeFormat = ajvFormats.get('time').validate
|
|
239
|
+
|
|
240
|
+
ajvInstance.addKeyword({
|
|
241
|
+
keyword: 'fjs_date_type',
|
|
242
|
+
validate: (schema, date) => {
|
|
243
|
+
if (date instanceof Date) {
|
|
244
|
+
return true
|
|
245
|
+
}
|
|
246
|
+
if (schema === 'date-time') {
|
|
247
|
+
return validateDateTimeFormat(date)
|
|
248
|
+
}
|
|
249
|
+
if (schema === 'date') {
|
|
250
|
+
return validateDateFormat(date)
|
|
251
|
+
}
|
|
252
|
+
if (schema === 'time') {
|
|
253
|
+
return validateTimeFormat(date)
|
|
254
|
+
}
|
|
255
|
+
return false
|
|
256
|
+
}
|
|
257
|
+
})
|
|
258
|
+
|
|
58
259
|
isValidSchema(schema)
|
|
59
260
|
if (options.schema) {
|
|
60
261
|
// eslint-disable-next-line
|
|
@@ -63,24 +264,29 @@ function build (schema, options) {
|
|
|
63
264
|
}
|
|
64
265
|
}
|
|
65
266
|
|
|
66
|
-
let intParseFunctionName = 'trunc'
|
|
67
267
|
if (options.rounding) {
|
|
68
|
-
if (['floor', 'ceil', 'round'].includes(options.rounding)) {
|
|
69
|
-
intParseFunctionName = options.rounding
|
|
70
|
-
} else {
|
|
268
|
+
if (!['floor', 'ceil', 'round'].includes(options.rounding)) {
|
|
71
269
|
throw new Error(`Unsupported integer rounding method ${options.rounding}`)
|
|
72
270
|
}
|
|
73
271
|
}
|
|
74
272
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
273
|
+
if (options.largeArrayMechanism) {
|
|
274
|
+
if (validLargeArrayMechanisms.includes(options.largeArrayMechanism)) {
|
|
275
|
+
largeArrayMechanism = options.largeArrayMechanism
|
|
276
|
+
} else {
|
|
277
|
+
throw new Error(`Unsupported large array mechanism ${options.rounding}`)
|
|
278
|
+
}
|
|
279
|
+
}
|
|
79
280
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
281
|
+
if (options.largeArraySize) {
|
|
282
|
+
if (!Number.isNaN(Number.parseInt(options.largeArraySize, 10))) {
|
|
283
|
+
largeArraySize = options.largeArraySize
|
|
284
|
+
} else {
|
|
285
|
+
throw new Error(`Unsupported large array size. Expected integer-like, got ${options.largeArraySize}`)
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
const serializer = new Serializer(options)
|
|
84
290
|
|
|
85
291
|
let location = {
|
|
86
292
|
schema,
|
|
@@ -97,61 +303,34 @@ function build (schema, options) {
|
|
|
97
303
|
schema.type = inferTypeByKeyword(schema)
|
|
98
304
|
}
|
|
99
305
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
code
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
main = schema.nullable ? '$asIntegerNullable' : '$asInteger'
|
|
112
|
-
break
|
|
113
|
-
case 'number':
|
|
114
|
-
main = schema.nullable ? '$asNumberNullable' : '$asNumber'
|
|
115
|
-
break
|
|
116
|
-
case 'boolean':
|
|
117
|
-
main = schema.nullable ? '$asBooleanNullable' : '$asBoolean'
|
|
118
|
-
break
|
|
119
|
-
case 'null':
|
|
120
|
-
main = '$asNull'
|
|
121
|
-
break
|
|
122
|
-
case 'array':
|
|
123
|
-
main = '$main'
|
|
124
|
-
code = buildArray(location, code, main)
|
|
125
|
-
schema = location.schema
|
|
126
|
-
break
|
|
127
|
-
case undefined:
|
|
128
|
-
main = '$asAny'
|
|
129
|
-
break
|
|
130
|
-
default:
|
|
131
|
-
throw new Error(`${schema.type} unsupported`)
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
code += `
|
|
135
|
-
;
|
|
136
|
-
return ${main}
|
|
137
|
-
`
|
|
138
|
-
const dependencies = [ajvInstance]
|
|
139
|
-
const dependenciesName = ['ajv']
|
|
140
|
-
ajvInstance = null
|
|
306
|
+
const { code, laterCode } = buildValue('', 'main', 'input', location)
|
|
307
|
+
const contextFunctionCode = `
|
|
308
|
+
'use strict'
|
|
309
|
+
function main (input) {
|
|
310
|
+
let json = ''
|
|
311
|
+
${code}
|
|
312
|
+
return json
|
|
313
|
+
}
|
|
314
|
+
${laterCode}
|
|
315
|
+
return main
|
|
316
|
+
`
|
|
141
317
|
|
|
142
|
-
dependenciesName
|
|
318
|
+
const dependenciesName = ['ajv', 'serializer', contextFunctionCode]
|
|
143
319
|
|
|
144
320
|
if (options.debugMode) {
|
|
145
|
-
return {
|
|
146
|
-
code: dependenciesName.join('\n'),
|
|
147
|
-
ajv: dependencies[0]
|
|
148
|
-
}
|
|
321
|
+
return { code: dependenciesName.join('\n'), ajv: ajvInstance }
|
|
149
322
|
}
|
|
150
323
|
|
|
324
|
+
/* eslint no-new-func: "off" */
|
|
325
|
+
const contextFunc = new Function('ajv', 'serializer', contextFunctionCode)
|
|
326
|
+
const stringifyFunc = contextFunc(ajvInstance, serializer)
|
|
327
|
+
|
|
328
|
+
ajvInstance = null
|
|
151
329
|
arrayItemsReferenceSerializersMap.clear()
|
|
152
330
|
objectReferenceSerializersMap.clear()
|
|
331
|
+
schemaReferenceMap.clear()
|
|
153
332
|
|
|
154
|
-
return
|
|
333
|
+
return stringifyFunc
|
|
155
334
|
}
|
|
156
335
|
|
|
157
336
|
const objectKeywords = [
|
|
@@ -189,7 +368,7 @@ const numberKeywords = [
|
|
|
189
368
|
|
|
190
369
|
/**
|
|
191
370
|
* Infer type based on keyword in order to generate optimized code
|
|
192
|
-
* https://
|
|
371
|
+
* https://datatracker.ietf.org/doc/html/draft-handrews-json-schema-validation-01#section-6
|
|
193
372
|
*/
|
|
194
373
|
function inferTypeByKeyword (schema) {
|
|
195
374
|
// eslint-disable-next-line
|
|
@@ -211,171 +390,15 @@ function inferTypeByKeyword (schema) {
|
|
|
211
390
|
return schema.type
|
|
212
391
|
}
|
|
213
392
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
function getStringSerializer (format) {
|
|
221
|
-
return stringSerializerMap[format] ||
|
|
222
|
-
'$asString'
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
function getTestSerializer (format) {
|
|
226
|
-
return stringSerializerMap[format]
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
const asFunctions = `
|
|
230
|
-
function $pad2Zeros (num) {
|
|
231
|
-
const s = '00' + num
|
|
232
|
-
return s[s.length - 2] + s[s.length - 1]
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
function $asAny (i) {
|
|
236
|
-
return JSON.stringify(i)
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
function $asNull () {
|
|
240
|
-
return 'null'
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
function $asInteger (i) {
|
|
244
|
-
if (typeof i === 'bigint') {
|
|
245
|
-
return i.toString()
|
|
246
|
-
} else if (Number.isInteger(i)) {
|
|
247
|
-
return $asNumber(i)
|
|
248
|
-
} else {
|
|
249
|
-
/* eslint no-undef: "off" */
|
|
250
|
-
return $asNumber(parseInteger(i))
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
function $asIntegerNullable (i) {
|
|
255
|
-
return i === null ? null : $asInteger(i)
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
function $asNumber (i) {
|
|
259
|
-
const num = Number(i)
|
|
260
|
-
if (isNaN(num)) {
|
|
261
|
-
return 'null'
|
|
262
|
-
} else {
|
|
263
|
-
return '' + num
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
function $asNumberNullable (i) {
|
|
268
|
-
return i === null ? null : $asNumber(i)
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
function $asBoolean (bool) {
|
|
272
|
-
return bool && 'true' || 'false' // eslint-disable-line
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
function $asBooleanNullable (bool) {
|
|
276
|
-
return bool === null ? null : $asBoolean(bool)
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
function $asDatetime (date, skipQuotes) {
|
|
280
|
-
const quotes = skipQuotes === true ? '' : '"'
|
|
281
|
-
if (date instanceof Date) {
|
|
282
|
-
return quotes + date.toISOString() + quotes
|
|
283
|
-
} else if (date && typeof date.toISOString === 'function') {
|
|
284
|
-
return quotes + date.toISOString() + quotes
|
|
285
|
-
} else {
|
|
286
|
-
return $asString(date, skipQuotes)
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
function $asDate (date, skipQuotes) {
|
|
291
|
-
const quotes = skipQuotes === true ? '' : '"'
|
|
292
|
-
if (date instanceof Date) {
|
|
293
|
-
return quotes + new Date(date.getTime() - (date.getTimezoneOffset() * 60000 )).toISOString().slice(0, 10) + quotes
|
|
294
|
-
} else if (date && typeof date.format === 'function') {
|
|
295
|
-
return quotes + date.format('YYYY-MM-DD') + quotes
|
|
296
|
-
} else {
|
|
297
|
-
return $asString(date, skipQuotes)
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
function $asTime (date, skipQuotes) {
|
|
302
|
-
const quotes = skipQuotes === true ? '' : '"'
|
|
303
|
-
if (date instanceof Date) {
|
|
304
|
-
const hour = new Intl.DateTimeFormat('en', { hour: 'numeric', hour12: false }).format(date)
|
|
305
|
-
const minute = new Intl.DateTimeFormat('en', { minute: 'numeric' }).format(date)
|
|
306
|
-
const second = new Intl.DateTimeFormat('en', { second: 'numeric' }).format(date)
|
|
307
|
-
return quotes + $pad2Zeros(hour) + ':' + $pad2Zeros(minute) + ':' + $pad2Zeros(second) + quotes
|
|
308
|
-
} else if (date && typeof date.format === 'function') {
|
|
309
|
-
return quotes + date.format('HH:mm:ss') + quotes
|
|
310
|
-
} else {
|
|
311
|
-
return $asString(date, skipQuotes)
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
function $asString (str, skipQuotes) {
|
|
316
|
-
const quotes = skipQuotes === true ? '' : '"'
|
|
317
|
-
if (str instanceof Date) {
|
|
318
|
-
return quotes + str.toISOString() + quotes
|
|
319
|
-
} else if (str === null) {
|
|
320
|
-
return quotes + quotes
|
|
321
|
-
} else if (str instanceof RegExp) {
|
|
322
|
-
str = str.source
|
|
323
|
-
} else if (typeof str !== 'string') {
|
|
324
|
-
str = str.toString()
|
|
325
|
-
}
|
|
326
|
-
// If we skipQuotes it means that we are using it as test
|
|
327
|
-
// no need to test the string length for the render
|
|
328
|
-
if (skipQuotes) {
|
|
329
|
-
return str
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
if (str.length < 42) {
|
|
333
|
-
return $asStringSmall(str)
|
|
334
|
-
} else {
|
|
335
|
-
return JSON.stringify(str)
|
|
393
|
+
function getStringSerializer (format, nullable) {
|
|
394
|
+
switch (format) {
|
|
395
|
+
case 'date-time': return nullable ? 'serializer.asDatetimeNullable.bind(serializer)' : 'serializer.asDatetime.bind(serializer)'
|
|
396
|
+
case 'date': return nullable ? 'serializer.asDateNullable.bind(serializer)' : 'serializer.asDate.bind(serializer)'
|
|
397
|
+
case 'time': return nullable ? 'serializer.asTimeNullable.bind(serializer)' : 'serializer.asTime.bind(serializer)'
|
|
398
|
+
default: return nullable ? 'serializer.asStringNullable.bind(serializer)' : 'serializer.asString.bind(serializer)'
|
|
336
399
|
}
|
|
337
400
|
}
|
|
338
401
|
|
|
339
|
-
function $asStringNullable (str) {
|
|
340
|
-
return str === null ? null : $asString(str)
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
// magically escape strings for json
|
|
344
|
-
// relying on their charCodeAt
|
|
345
|
-
// everything below 32 needs JSON.stringify()
|
|
346
|
-
// every string that contain surrogate needs JSON.stringify()
|
|
347
|
-
// 34 and 92 happens all the time, so we
|
|
348
|
-
// have a fast case for them
|
|
349
|
-
function $asStringSmall (str) {
|
|
350
|
-
const l = str.length
|
|
351
|
-
let result = ''
|
|
352
|
-
let last = 0
|
|
353
|
-
let found = false
|
|
354
|
-
let surrogateFound = false
|
|
355
|
-
let point = 255
|
|
356
|
-
// eslint-disable-next-line
|
|
357
|
-
for (var i = 0; i < l && point >= 32; i++) {
|
|
358
|
-
point = str.charCodeAt(i)
|
|
359
|
-
if (point >= 0xD800 && point <= 0xDFFF) {
|
|
360
|
-
// The current character is a surrogate.
|
|
361
|
-
surrogateFound = true
|
|
362
|
-
}
|
|
363
|
-
if (point === 34 || point === 92) {
|
|
364
|
-
result += str.slice(last, i) + '\\\\'
|
|
365
|
-
last = i
|
|
366
|
-
found = true
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
if (!found) {
|
|
371
|
-
result = str
|
|
372
|
-
} else {
|
|
373
|
-
result += str.slice(last)
|
|
374
|
-
}
|
|
375
|
-
return ((point < 32) || (surrogateFound === true)) ? JSON.stringify(str) : '"' + result + '"'
|
|
376
|
-
}
|
|
377
|
-
`
|
|
378
|
-
|
|
379
402
|
function addPatternProperties (location) {
|
|
380
403
|
const schema = location.schema
|
|
381
404
|
const pp = schema.patternProperties
|
|
@@ -385,103 +408,56 @@ function addPatternProperties (location) {
|
|
|
385
408
|
for (var i = 0; i < keys.length; i++) {
|
|
386
409
|
if (properties[keys[i]]) continue
|
|
387
410
|
`
|
|
411
|
+
let laterCode = ''
|
|
412
|
+
|
|
388
413
|
Object.keys(pp).forEach((regex, index) => {
|
|
389
414
|
let ppLocation = mergeLocation(location, { schema: pp[regex] })
|
|
390
415
|
if (pp[regex].$ref) {
|
|
391
416
|
ppLocation = refFinder(pp[regex].$ref, location)
|
|
392
417
|
pp[regex] = ppLocation.schema
|
|
393
418
|
}
|
|
394
|
-
|
|
395
|
-
const format = pp[regex].format
|
|
396
|
-
const stringSerializer = getStringSerializer(format)
|
|
419
|
+
|
|
397
420
|
try {
|
|
398
421
|
RegExp(regex)
|
|
399
422
|
} catch (err) {
|
|
400
423
|
throw new Error(`${err.message}. Found at ${regex} matching ${JSON.stringify(pp[regex])}`)
|
|
401
424
|
}
|
|
402
425
|
|
|
403
|
-
const
|
|
404
|
-
|
|
405
|
-
if (type === 'object') {
|
|
406
|
-
code += `${buildObject(ppLocation, '', 'buildObjectPP' + index)}
|
|
407
|
-
${ifPpKeyExists}
|
|
408
|
-
${addComma}
|
|
409
|
-
json += $asString(keys[i]) + ':' + buildObjectPP${index}(obj[keys[i]])
|
|
410
|
-
`
|
|
411
|
-
} else if (type === 'array') {
|
|
412
|
-
code += `${buildArray(ppLocation, '', 'buildArrayPP' + index)}
|
|
413
|
-
${ifPpKeyExists}
|
|
414
|
-
${addComma}
|
|
415
|
-
json += $asString(keys[i]) + ':' + buildArrayPP${index}(obj[keys[i]])
|
|
416
|
-
`
|
|
417
|
-
} else if (type === 'null') {
|
|
418
|
-
code += `
|
|
419
|
-
${ifPpKeyExists}
|
|
420
|
-
${addComma}
|
|
421
|
-
json += $asString(keys[i]) +':null'
|
|
422
|
-
`
|
|
423
|
-
} else if (type === 'string') {
|
|
424
|
-
code += `
|
|
425
|
-
${ifPpKeyExists}
|
|
426
|
-
${addComma}
|
|
427
|
-
json += $asString(keys[i]) + ':' + ${stringSerializer}(obj[keys[i]])
|
|
428
|
-
`
|
|
429
|
-
} else if (type === 'integer') {
|
|
430
|
-
code += `
|
|
431
|
-
${ifPpKeyExists}
|
|
432
|
-
${addComma}
|
|
433
|
-
json += $asString(keys[i]) + ':' + $asInteger(obj[keys[i]])
|
|
434
|
-
`
|
|
435
|
-
} else if (type === 'number') {
|
|
436
|
-
code += `
|
|
437
|
-
${ifPpKeyExists}
|
|
438
|
-
${addComma}
|
|
439
|
-
json += $asString(keys[i]) + ':' + $asNumber(obj[keys[i]])
|
|
440
|
-
`
|
|
441
|
-
} else if (type === 'boolean') {
|
|
442
|
-
code += `
|
|
443
|
-
${ifPpKeyExists}
|
|
444
|
-
${addComma}
|
|
445
|
-
json += $asString(keys[i]) + ':' + $asBoolean(obj[keys[i]])
|
|
446
|
-
`
|
|
447
|
-
} else if (type === undefined) {
|
|
448
|
-
code += `
|
|
449
|
-
${ifPpKeyExists}
|
|
450
|
-
${addComma}
|
|
451
|
-
json += $asString(keys[i]) + ':' + $asAny(obj[keys[i]])
|
|
452
|
-
`
|
|
453
|
-
} else {
|
|
454
|
-
code += `
|
|
455
|
-
${ifPpKeyExists}
|
|
456
|
-
throw new Error('Cannot coerce ' + obj[keys[i]] + ' to ' + ${JSON.stringify(type)})
|
|
457
|
-
`
|
|
458
|
-
}
|
|
459
|
-
|
|
426
|
+
const valueCode = buildValue('', '', 'obj[keys[i]]', ppLocation)
|
|
427
|
+
laterCode += valueCode.laterCode
|
|
460
428
|
code += `
|
|
461
|
-
|
|
462
|
-
}
|
|
429
|
+
if (/${regex.replace(/\\*\//g, '\\/')}/.test(keys[i])) {
|
|
430
|
+
${addComma}
|
|
431
|
+
json += serializer.asString(keys[i]) + ':'
|
|
432
|
+
${valueCode.code}
|
|
433
|
+
continue
|
|
434
|
+
}
|
|
463
435
|
`
|
|
464
436
|
})
|
|
465
437
|
if (schema.additionalProperties) {
|
|
466
|
-
|
|
438
|
+
const additionalPropertyCode = additionalProperty(location)
|
|
439
|
+
code += additionalPropertyCode.code
|
|
440
|
+
laterCode += additionalPropertyCode.laterCode
|
|
467
441
|
}
|
|
468
442
|
|
|
469
443
|
code += `
|
|
470
444
|
}
|
|
471
445
|
`
|
|
472
|
-
return code
|
|
446
|
+
return { code, laterCode }
|
|
473
447
|
}
|
|
474
448
|
|
|
475
449
|
function additionalProperty (location) {
|
|
476
450
|
let ap = location.schema.additionalProperties
|
|
477
451
|
let code = ''
|
|
478
452
|
if (ap === true) {
|
|
479
|
-
|
|
453
|
+
code += `
|
|
480
454
|
if (obj[keys[i]] !== undefined && typeof obj[keys[i]] !== 'function' && typeof obj[keys[i]] !== 'symbol') {
|
|
481
455
|
${addComma}
|
|
482
|
-
json +=
|
|
456
|
+
json += serializer.asString(keys[i]) + ':' + JSON.stringify(obj[keys[i]])
|
|
483
457
|
}
|
|
484
458
|
`
|
|
459
|
+
|
|
460
|
+
return { code, laterCode: '' }
|
|
485
461
|
}
|
|
486
462
|
let apLocation = mergeLocation(location, { schema: ap })
|
|
487
463
|
if (ap.$ref) {
|
|
@@ -489,72 +465,29 @@ function additionalProperty (location) {
|
|
|
489
465
|
ap = apLocation.schema
|
|
490
466
|
}
|
|
491
467
|
|
|
492
|
-
const
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
code += `${buildArray(apLocation, '', 'buildArrayAP')}
|
|
502
|
-
${addComma}
|
|
503
|
-
json += $asString(keys[i]) + ':' + buildArrayAP(obj[keys[i]])
|
|
504
|
-
`
|
|
505
|
-
} else if (type === 'null') {
|
|
506
|
-
code += `
|
|
507
|
-
${addComma}
|
|
508
|
-
json += $asString(keys[i]) +':null'
|
|
509
|
-
`
|
|
510
|
-
} else if (type === 'string') {
|
|
511
|
-
code += `
|
|
512
|
-
${addComma}
|
|
513
|
-
json += $asString(keys[i]) + ':' + ${stringSerializer}(obj[keys[i]])
|
|
514
|
-
`
|
|
515
|
-
} else if (type === 'integer') {
|
|
516
|
-
code += `
|
|
517
|
-
var t = Number(obj[keys[i]])
|
|
518
|
-
if (!isNaN(t)) {
|
|
519
|
-
${addComma}
|
|
520
|
-
json += $asString(keys[i]) + ':' + t
|
|
521
|
-
}
|
|
522
|
-
`
|
|
523
|
-
} else if (type === 'number') {
|
|
524
|
-
code += `
|
|
525
|
-
var t = Number(obj[keys[i]])
|
|
526
|
-
if (!isNaN(t)) {
|
|
527
|
-
${addComma}
|
|
528
|
-
json += $asString(keys[i]) + ':' + t
|
|
529
|
-
}
|
|
530
|
-
`
|
|
531
|
-
} else if (type === 'boolean') {
|
|
532
|
-
code += `
|
|
533
|
-
${addComma}
|
|
534
|
-
json += $asString(keys[i]) + ':' + $asBoolean(obj[keys[i]])
|
|
535
|
-
`
|
|
536
|
-
} else if (type === undefined) {
|
|
537
|
-
code += `
|
|
538
|
-
${addComma}
|
|
539
|
-
json += $asString(keys[i]) + ':' + $asAny(obj[keys[i]])
|
|
540
|
-
`
|
|
541
|
-
} else {
|
|
542
|
-
code += `
|
|
543
|
-
throw new Error('Cannot coerce ' + obj[keys[i]] + ' to ' + ${JSON.stringify(type)})
|
|
544
|
-
`
|
|
545
|
-
}
|
|
546
|
-
return code
|
|
468
|
+
const valueCode = buildValue('', '', 'obj[keys[i]]', apLocation)
|
|
469
|
+
|
|
470
|
+
code += `
|
|
471
|
+
${addComma}
|
|
472
|
+
json += serializer.asString(keys[i]) + ':'
|
|
473
|
+
${valueCode.code}
|
|
474
|
+
`
|
|
475
|
+
|
|
476
|
+
return { code, laterCode: valueCode.laterCode }
|
|
547
477
|
}
|
|
548
478
|
|
|
549
479
|
function addAdditionalProperties (location) {
|
|
550
|
-
|
|
480
|
+
const additionalPropertyCode = additionalProperty(location)
|
|
481
|
+
const code = `
|
|
551
482
|
var properties = ${JSON.stringify(location.schema.properties)} || {}
|
|
552
483
|
var keys = Object.keys(obj)
|
|
553
484
|
for (var i = 0; i < keys.length; i++) {
|
|
554
485
|
if (properties[keys[i]]) continue
|
|
555
|
-
${
|
|
486
|
+
${additionalPropertyCode.code}
|
|
556
487
|
}
|
|
557
488
|
`
|
|
489
|
+
|
|
490
|
+
return { code, laterCode: additionalPropertyCode.laterCode }
|
|
558
491
|
}
|
|
559
492
|
|
|
560
493
|
function idFinder (schema, searchedId) {
|
|
@@ -581,15 +514,25 @@ function refFinder (ref, location) {
|
|
|
581
514
|
return {
|
|
582
515
|
schema: externalSchema[ref],
|
|
583
516
|
root: externalSchema[ref],
|
|
584
|
-
externalSchema
|
|
517
|
+
externalSchema
|
|
585
518
|
}
|
|
586
519
|
}
|
|
587
520
|
|
|
588
521
|
// Split file from walk
|
|
589
522
|
ref = ref.split('#')
|
|
590
523
|
|
|
591
|
-
//
|
|
592
|
-
if (ref[0]) {
|
|
524
|
+
// Check schemaReferenceMap for $id entry
|
|
525
|
+
if (ref[0] && schemaReferenceMap.has(ref[0])) {
|
|
526
|
+
schema = schemaReferenceMap.get(ref[0])
|
|
527
|
+
root = schemaReferenceMap.get(ref[0])
|
|
528
|
+
if (schema.$ref) {
|
|
529
|
+
return refFinder(schema.$ref, {
|
|
530
|
+
schema,
|
|
531
|
+
root,
|
|
532
|
+
externalSchema
|
|
533
|
+
})
|
|
534
|
+
}
|
|
535
|
+
} else if (ref[0]) { // If external file
|
|
593
536
|
schema = externalSchema[ref[0]]
|
|
594
537
|
root = externalSchema[ref[0]]
|
|
595
538
|
|
|
@@ -599,9 +542,9 @@ function refFinder (ref, location) {
|
|
|
599
542
|
|
|
600
543
|
if (schema.$ref) {
|
|
601
544
|
return refFinder(schema.$ref, {
|
|
602
|
-
schema
|
|
603
|
-
root
|
|
604
|
-
externalSchema
|
|
545
|
+
schema,
|
|
546
|
+
root,
|
|
547
|
+
externalSchema
|
|
605
548
|
})
|
|
606
549
|
}
|
|
607
550
|
}
|
|
@@ -628,8 +571,8 @@ function refFinder (ref, location) {
|
|
|
628
571
|
|
|
629
572
|
return {
|
|
630
573
|
schema: dereferenced,
|
|
631
|
-
root
|
|
632
|
-
externalSchema
|
|
574
|
+
root,
|
|
575
|
+
externalSchema
|
|
633
576
|
}
|
|
634
577
|
} else {
|
|
635
578
|
// eslint-disable-next-line
|
|
@@ -650,16 +593,16 @@ function refFinder (ref, location) {
|
|
|
650
593
|
|
|
651
594
|
if (result.$ref) {
|
|
652
595
|
return refFinder(result.$ref, {
|
|
653
|
-
schema
|
|
654
|
-
root
|
|
655
|
-
externalSchema
|
|
596
|
+
schema,
|
|
597
|
+
root,
|
|
598
|
+
externalSchema
|
|
656
599
|
})
|
|
657
600
|
}
|
|
658
601
|
|
|
659
602
|
return {
|
|
660
603
|
schema: result,
|
|
661
|
-
root
|
|
662
|
-
externalSchema
|
|
604
|
+
root,
|
|
605
|
+
externalSchema
|
|
663
606
|
}
|
|
664
607
|
|
|
665
608
|
function findBadKey (obj, keys) {
|
|
@@ -678,15 +621,15 @@ function refFinder (ref, location) {
|
|
|
678
621
|
}
|
|
679
622
|
}
|
|
680
623
|
|
|
681
|
-
function buildCode (location, code, laterCode,
|
|
624
|
+
function buildCode (location, code, laterCode, locationPath) {
|
|
682
625
|
if (location.schema.$ref) {
|
|
683
626
|
location = refFinder(location.schema.$ref, location)
|
|
684
627
|
}
|
|
685
628
|
|
|
686
629
|
const schema = location.schema
|
|
687
|
-
|
|
630
|
+
const required = schema.required || []
|
|
688
631
|
|
|
689
|
-
Object.keys(schema.properties || {}).forEach((key
|
|
632
|
+
Object.keys(schema.properties || {}).forEach((key) => {
|
|
690
633
|
let propertyLocation = mergeLocation(location, { schema: schema.properties[key] })
|
|
691
634
|
if (schema.properties[key].$ref) {
|
|
692
635
|
propertyLocation = refFinder(schema.properties[key].$ref, location)
|
|
@@ -696,61 +639,27 @@ function buildCode (location, code, laterCode, name) {
|
|
|
696
639
|
// Using obj['key'] !== undefined instead of obj.hasOwnProperty(prop) for perf reasons,
|
|
697
640
|
// see https://github.com/mcollina/fast-json-stringify/pull/3 for discussion.
|
|
698
641
|
|
|
699
|
-
const type = schema.properties[key].type
|
|
700
|
-
const nullable = schema.properties[key].nullable
|
|
701
642
|
const sanitized = JSON.stringify(key)
|
|
702
643
|
const asString = JSON.stringify(sanitized)
|
|
703
644
|
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
json += ${asString} + ':null'
|
|
709
|
-
var rendered = true
|
|
710
|
-
} else {
|
|
645
|
+
code += `
|
|
646
|
+
if (obj[${sanitized}] !== undefined) {
|
|
647
|
+
${addComma}
|
|
648
|
+
json += ${asString} + ':'
|
|
711
649
|
`
|
|
712
|
-
}
|
|
713
650
|
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
if (!isNaN(t)) {
|
|
718
|
-
${addComma}
|
|
719
|
-
json += ${asString} + ':' + t
|
|
720
|
-
`
|
|
721
|
-
} else if (type === 'integer') {
|
|
722
|
-
code += `
|
|
723
|
-
var rendered = false
|
|
724
|
-
var t = $asInteger(obj[${sanitized}])
|
|
725
|
-
if (!isNaN(t)) {
|
|
726
|
-
${addComma}
|
|
727
|
-
json += ${asString} + ':' + t
|
|
728
|
-
rendered = true
|
|
729
|
-
}
|
|
730
|
-
if (rendered) {
|
|
731
|
-
`
|
|
732
|
-
} else {
|
|
733
|
-
code += `
|
|
734
|
-
if (obj[${sanitized}] !== undefined) {
|
|
735
|
-
${addComma}
|
|
736
|
-
json += ${asString} + ':'
|
|
737
|
-
`
|
|
738
|
-
|
|
739
|
-
const result = nested(laterCode, name, key, mergeLocation(propertyLocation, { schema: schema.properties[key] }), undefined, false)
|
|
740
|
-
code += result.code
|
|
741
|
-
laterCode = result.laterCode
|
|
742
|
-
}
|
|
651
|
+
const result = buildValue(laterCode, locationPath + key, `obj[${JSON.stringify(key)}]`, mergeLocation(propertyLocation, { schema: schema.properties[key] }))
|
|
652
|
+
code += result.code
|
|
653
|
+
laterCode = result.laterCode
|
|
743
654
|
|
|
744
655
|
const defaultValue = schema.properties[key].default
|
|
745
656
|
if (defaultValue !== undefined) {
|
|
746
|
-
required = filterRequired(required, key)
|
|
747
657
|
code += `
|
|
748
658
|
} else {
|
|
749
659
|
${addComma}
|
|
750
660
|
json += ${asString} + ':' + ${JSON.stringify(JSON.stringify(defaultValue))}
|
|
751
661
|
`
|
|
752
|
-
} else if (required
|
|
753
|
-
required = filterRequired(required, key)
|
|
662
|
+
} else if (required.includes(key)) {
|
|
754
663
|
code += `
|
|
755
664
|
} else {
|
|
756
665
|
throw new Error('${sanitized} is required!')
|
|
@@ -760,75 +669,122 @@ function buildCode (location, code, laterCode, name) {
|
|
|
760
669
|
code += `
|
|
761
670
|
}
|
|
762
671
|
`
|
|
672
|
+
})
|
|
763
673
|
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
674
|
+
for (const requiredProperty of required) {
|
|
675
|
+
if (schema.properties && schema.properties[requiredProperty] !== undefined) continue
|
|
676
|
+
code += `if (obj['${requiredProperty}'] === undefined) throw new Error('"${requiredProperty}" is required!')\n`
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
return { code, laterCode }
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
function mergeAllOfSchema (location, schema, mergedSchema) {
|
|
683
|
+
for (let allOfSchema of schema.allOf) {
|
|
684
|
+
if (allOfSchema.$ref) {
|
|
685
|
+
allOfSchema = refFinder(allOfSchema.$ref, mergeLocation(location, { schema: allOfSchema })).schema
|
|
768
686
|
}
|
|
769
|
-
})
|
|
770
687
|
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
688
|
+
let allOfSchemaType = allOfSchema.type
|
|
689
|
+
if (allOfSchemaType === undefined) {
|
|
690
|
+
allOfSchemaType = inferTypeByKeyword(allOfSchema)
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
if (allOfSchemaType !== undefined) {
|
|
694
|
+
if (
|
|
695
|
+
mergedSchema.type !== undefined &&
|
|
696
|
+
mergedSchema.type !== allOfSchemaType
|
|
697
|
+
) {
|
|
698
|
+
throw new Error('allOf schemas have different type values')
|
|
777
699
|
}
|
|
778
|
-
|
|
700
|
+
mergedSchema.type = allOfSchemaType
|
|
779
701
|
}
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
702
|
+
|
|
703
|
+
if (allOfSchema.format !== undefined) {
|
|
704
|
+
if (
|
|
705
|
+
mergedSchema.format !== undefined &&
|
|
706
|
+
mergedSchema.format !== allOfSchema.format
|
|
707
|
+
) {
|
|
708
|
+
throw new Error('allOf schemas have different format values')
|
|
783
709
|
}
|
|
784
|
-
|
|
785
|
-
|
|
710
|
+
mergedSchema.format = allOfSchema.format
|
|
711
|
+
}
|
|
786
712
|
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
713
|
+
if (allOfSchema.nullable !== undefined) {
|
|
714
|
+
if (
|
|
715
|
+
mergedSchema.nullable !== undefined &&
|
|
716
|
+
mergedSchema.nullable !== allOfSchema.nullable
|
|
717
|
+
) {
|
|
718
|
+
throw new Error('allOf schemas have different nullable values')
|
|
719
|
+
}
|
|
720
|
+
mergedSchema.nullable = allOfSchema.nullable
|
|
721
|
+
}
|
|
792
722
|
|
|
793
|
-
|
|
794
|
-
|
|
723
|
+
if (allOfSchema.properties !== undefined) {
|
|
724
|
+
if (mergedSchema.properties === undefined) {
|
|
725
|
+
mergedSchema.properties = {}
|
|
726
|
+
}
|
|
727
|
+
Object.assign(mergedSchema.properties, allOfSchema.properties)
|
|
728
|
+
}
|
|
795
729
|
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
}
|
|
730
|
+
if (allOfSchema.additionalProperties !== undefined) {
|
|
731
|
+
if (mergedSchema.additionalProperties === undefined) {
|
|
732
|
+
mergedSchema.additionalProperties = {}
|
|
733
|
+
}
|
|
734
|
+
Object.assign(mergedSchema.additionalProperties, allOfSchema.additionalProperties)
|
|
735
|
+
}
|
|
802
736
|
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
})
|
|
810
|
-
} else {
|
|
811
|
-
const builtCode = buildCode(location, code, laterCode, name)
|
|
737
|
+
if (allOfSchema.patternProperties !== undefined) {
|
|
738
|
+
if (mergedSchema.patternProperties === undefined) {
|
|
739
|
+
mergedSchema.patternProperties = {}
|
|
740
|
+
}
|
|
741
|
+
Object.assign(mergedSchema.patternProperties, allOfSchema.patternProperties)
|
|
742
|
+
}
|
|
812
743
|
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
744
|
+
if (allOfSchema.required !== undefined) {
|
|
745
|
+
if (mergedSchema.required === undefined) {
|
|
746
|
+
mergedSchema.required = []
|
|
747
|
+
}
|
|
748
|
+
mergedSchema.required.push(...allOfSchema.required)
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
if (allOfSchema.oneOf !== undefined) {
|
|
752
|
+
if (mergedSchema.oneOf === undefined) {
|
|
753
|
+
mergedSchema.oneOf = []
|
|
754
|
+
}
|
|
755
|
+
mergedSchema.oneOf.push(...allOfSchema.oneOf)
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
if (allOfSchema.anyOf !== undefined) {
|
|
759
|
+
if (mergedSchema.anyOf === undefined) {
|
|
760
|
+
mergedSchema.anyOf = []
|
|
761
|
+
}
|
|
762
|
+
mergedSchema.anyOf.push(...allOfSchema.anyOf)
|
|
763
|
+
}
|
|
816
764
|
|
|
817
|
-
|
|
765
|
+
if (allOfSchema.allOf !== undefined) {
|
|
766
|
+
mergeAllOfSchema(location, allOfSchema, mergedSchema)
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
delete mergedSchema.allOf
|
|
818
770
|
}
|
|
819
771
|
|
|
820
|
-
function buildInnerObject (location,
|
|
772
|
+
function buildInnerObject (location, locationPath) {
|
|
821
773
|
const schema = location.schema
|
|
822
|
-
const result =
|
|
774
|
+
const result = buildCode(location, '', '', locationPath)
|
|
823
775
|
if (schema.patternProperties) {
|
|
824
|
-
|
|
776
|
+
const { code, laterCode } = addPatternProperties(location)
|
|
777
|
+
result.code += code
|
|
778
|
+
result.laterCode += laterCode
|
|
825
779
|
} else if (schema.additionalProperties && !schema.patternProperties) {
|
|
826
|
-
|
|
780
|
+
const { code, laterCode } = addAdditionalProperties(location)
|
|
781
|
+
result.code += code
|
|
782
|
+
result.laterCode += laterCode
|
|
827
783
|
}
|
|
828
784
|
return result
|
|
829
785
|
}
|
|
830
786
|
|
|
831
|
-
function addIfThenElse (location,
|
|
787
|
+
function addIfThenElse (location, locationPath) {
|
|
832
788
|
let code = ''
|
|
833
789
|
let r
|
|
834
790
|
let laterCode = ''
|
|
@@ -853,12 +809,12 @@ function addIfThenElse (location, name) {
|
|
|
853
809
|
if (valid) {
|
|
854
810
|
`
|
|
855
811
|
if (merged.if && merged.then) {
|
|
856
|
-
innerR = addIfThenElse(mergedLocation,
|
|
812
|
+
innerR = addIfThenElse(mergedLocation, locationPath + 'Then')
|
|
857
813
|
code += innerR.code
|
|
858
814
|
laterCode = innerR.laterCode
|
|
859
815
|
}
|
|
860
816
|
|
|
861
|
-
r = buildInnerObject(mergedLocation,
|
|
817
|
+
r = buildInnerObject(mergedLocation, locationPath + 'Then')
|
|
862
818
|
code += r.code
|
|
863
819
|
laterCode += r.laterCode
|
|
864
820
|
|
|
@@ -873,19 +829,19 @@ function addIfThenElse (location, name) {
|
|
|
873
829
|
`
|
|
874
830
|
|
|
875
831
|
if (merged.if && merged.then) {
|
|
876
|
-
innerR = addIfThenElse(mergedLocation,
|
|
832
|
+
innerR = addIfThenElse(mergedLocation, locationPath + 'Else')
|
|
877
833
|
code += innerR.code
|
|
878
834
|
laterCode += innerR.laterCode
|
|
879
835
|
}
|
|
880
836
|
|
|
881
|
-
r = buildInnerObject(mergedLocation,
|
|
837
|
+
r = buildInnerObject(mergedLocation, locationPath + 'Else')
|
|
882
838
|
code += r.code
|
|
883
839
|
laterCode += r.laterCode
|
|
884
840
|
|
|
885
841
|
code += `
|
|
886
842
|
}
|
|
887
843
|
`
|
|
888
|
-
return { code
|
|
844
|
+
return { code, laterCode }
|
|
889
845
|
}
|
|
890
846
|
|
|
891
847
|
function toJSON (variableName) {
|
|
@@ -895,11 +851,14 @@ function toJSON (variableName) {
|
|
|
895
851
|
`
|
|
896
852
|
}
|
|
897
853
|
|
|
898
|
-
function buildObject (location, code,
|
|
854
|
+
function buildObject (location, code, functionName, locationPath) {
|
|
899
855
|
const schema = location.schema
|
|
900
|
-
|
|
856
|
+
if (schema.$id !== undefined) {
|
|
857
|
+
schemaReferenceMap.set(schema.$id, schema)
|
|
858
|
+
}
|
|
901
859
|
code += `
|
|
902
|
-
function ${
|
|
860
|
+
function ${functionName} (input) {
|
|
861
|
+
// ${locationPath}
|
|
903
862
|
`
|
|
904
863
|
if (schema.nullable) {
|
|
905
864
|
code += `
|
|
@@ -909,14 +868,14 @@ function buildObject (location, code, name) {
|
|
|
909
868
|
`
|
|
910
869
|
}
|
|
911
870
|
|
|
912
|
-
if (objectReferenceSerializersMap.has(schema) && objectReferenceSerializersMap.get(schema) !==
|
|
871
|
+
if (objectReferenceSerializersMap.has(schema) && objectReferenceSerializersMap.get(schema) !== functionName) {
|
|
913
872
|
code += `
|
|
914
873
|
return ${objectReferenceSerializersMap.get(schema)}(input)
|
|
915
874
|
}
|
|
916
875
|
`
|
|
917
876
|
return code
|
|
918
877
|
}
|
|
919
|
-
objectReferenceSerializersMap.set(schema,
|
|
878
|
+
objectReferenceSerializersMap.set(schema, functionName)
|
|
920
879
|
|
|
921
880
|
code += `
|
|
922
881
|
var obj = ${toJSON('input')}
|
|
@@ -929,9 +888,9 @@ function buildObject (location, code, name) {
|
|
|
929
888
|
code += `
|
|
930
889
|
var valid
|
|
931
890
|
`
|
|
932
|
-
r = addIfThenElse(location,
|
|
891
|
+
r = addIfThenElse(location, locationPath)
|
|
933
892
|
} else {
|
|
934
|
-
r = buildInnerObject(location,
|
|
893
|
+
r = buildInnerObject(location, locationPath)
|
|
935
894
|
}
|
|
936
895
|
|
|
937
896
|
// Removes the comma if is the last element of the string (in case there are not properties)
|
|
@@ -945,10 +904,14 @@ function buildObject (location, code, name) {
|
|
|
945
904
|
return code
|
|
946
905
|
}
|
|
947
906
|
|
|
948
|
-
function buildArray (location, code,
|
|
907
|
+
function buildArray (location, code, functionName, locationPath) {
|
|
949
908
|
let schema = location.schema
|
|
909
|
+
if (schema.$id !== undefined) {
|
|
910
|
+
schemaReferenceMap.set(schema.$id, schema)
|
|
911
|
+
}
|
|
950
912
|
code += `
|
|
951
|
-
function ${
|
|
913
|
+
function ${functionName} (obj) {
|
|
914
|
+
// ${locationPath}
|
|
952
915
|
`
|
|
953
916
|
if (schema.nullable) {
|
|
954
917
|
code += `
|
|
@@ -981,14 +944,14 @@ function buildArray (location, code, name, key = null) {
|
|
|
981
944
|
`
|
|
982
945
|
return code
|
|
983
946
|
}
|
|
984
|
-
arrayItemsReferenceSerializersMap.set(schema.items,
|
|
947
|
+
arrayItemsReferenceSerializersMap.set(schema.items, functionName)
|
|
985
948
|
}
|
|
986
949
|
|
|
987
950
|
let result = { code: '', laterCode: '' }
|
|
988
951
|
const accessor = '[i]'
|
|
989
952
|
if (Array.isArray(schema.items)) {
|
|
990
953
|
result = schema.items.reduce((res, item, i) => {
|
|
991
|
-
const tmpRes =
|
|
954
|
+
const tmpRes = buildValue(laterCode, locationPath + accessor + i, 'obj[i]', mergeLocation(location, { schema: item }))
|
|
992
955
|
const condition = `i === ${i} && ${buildArrayTypeCondition(item.type, accessor)}`
|
|
993
956
|
return {
|
|
994
957
|
code: `${res.code}
|
|
@@ -1001,7 +964,7 @@ function buildArray (location, code, name, key = null) {
|
|
|
1001
964
|
}, result)
|
|
1002
965
|
|
|
1003
966
|
if (schema.additionalItems) {
|
|
1004
|
-
const tmpRes =
|
|
967
|
+
const tmpRes = buildValue(laterCode, locationPath + accessor, 'obj[i]', mergeLocation(location, { schema: schema.items }))
|
|
1005
968
|
result.code += `
|
|
1006
969
|
else if (i >= ${schema.items.length}) {
|
|
1007
970
|
${tmpRes.code}
|
|
@@ -1015,31 +978,39 @@ function buildArray (location, code, name, key = null) {
|
|
|
1015
978
|
}
|
|
1016
979
|
`
|
|
1017
980
|
} else {
|
|
1018
|
-
result =
|
|
981
|
+
result = buildValue(laterCode, locationPath + accessor, 'obj[i]', mergeLocation(location, { schema: schema.items }))
|
|
1019
982
|
}
|
|
1020
983
|
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
984
|
+
code += `
|
|
985
|
+
if (!Array.isArray(obj)) {
|
|
986
|
+
throw new TypeError(\`The value '$\{obj}' does not match schema definition.\`)
|
|
987
|
+
}
|
|
988
|
+
`
|
|
989
|
+
|
|
990
|
+
code += 'const arrayLength = obj.length\n'
|
|
991
|
+
if (largeArrayMechanism !== 'default') {
|
|
992
|
+
if (largeArrayMechanism === 'json-stringify') {
|
|
993
|
+
code += `if (arrayLength && arrayLength >= ${largeArraySize}) return JSON.stringify(obj)\n`
|
|
994
|
+
} else {
|
|
995
|
+
throw new Error(`Unsupported large array mechanism ${largeArrayMechanism}`)
|
|
1025
996
|
}
|
|
1026
|
-
`
|
|
1027
997
|
}
|
|
1028
998
|
|
|
1029
999
|
code += `
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
var json = ''
|
|
1000
|
+
let jsonOutput= ''
|
|
1001
|
+
for (let i = 0; i < arrayLength; i++) {
|
|
1002
|
+
let json = ''
|
|
1034
1003
|
${result.code}
|
|
1035
1004
|
jsonOutput += json
|
|
1036
1005
|
|
|
1037
|
-
if (json.length > 0 && i <
|
|
1006
|
+
if (json.length > 0 && i < arrayLength - 1) {
|
|
1038
1007
|
jsonOutput += ','
|
|
1039
1008
|
}
|
|
1040
1009
|
}
|
|
1041
1010
|
return \`[\${jsonOutput}]\`
|
|
1042
|
-
}
|
|
1011
|
+
}`
|
|
1012
|
+
|
|
1013
|
+
code += `
|
|
1043
1014
|
${result.laterCode}
|
|
1044
1015
|
`
|
|
1045
1016
|
|
|
@@ -1107,26 +1078,12 @@ function dereferenceOfRefs (location, type) {
|
|
|
1107
1078
|
return locations
|
|
1108
1079
|
}
|
|
1109
1080
|
|
|
1110
|
-
let
|
|
1111
|
-
function
|
|
1112
|
-
|
|
1113
|
-
let rep = str.replace(/[^a-zA-Z0-9$_]/g, '')
|
|
1114
|
-
|
|
1115
|
-
if (rep.length === 0) {
|
|
1116
|
-
return 'anan' + strNameCounter++
|
|
1117
|
-
} else if (rep !== str) {
|
|
1118
|
-
rep += strNameCounter++
|
|
1119
|
-
}
|
|
1120
|
-
|
|
1121
|
-
return rep
|
|
1081
|
+
let genFuncNameCounter = 0
|
|
1082
|
+
function generateFuncName () {
|
|
1083
|
+
return 'anonymous' + genFuncNameCounter++
|
|
1122
1084
|
}
|
|
1123
1085
|
|
|
1124
|
-
function
|
|
1125
|
-
let code = ''
|
|
1126
|
-
let funcName
|
|
1127
|
-
|
|
1128
|
-
subKey = subKey || ''
|
|
1129
|
-
|
|
1086
|
+
function buildValue (laterCode, locationPath, input, location) {
|
|
1130
1087
|
let schema = location.schema
|
|
1131
1088
|
|
|
1132
1089
|
if (schema.$ref) {
|
|
@@ -1140,115 +1097,99 @@ function nested (laterCode, name, key, location, subKey, isArray) {
|
|
|
1140
1097
|
}
|
|
1141
1098
|
}
|
|
1142
1099
|
|
|
1100
|
+
if (schema.allOf) {
|
|
1101
|
+
const mergedSchema = clone(schema)
|
|
1102
|
+
mergeAllOfSchema(location, schema, mergedSchema)
|
|
1103
|
+
schema = mergedSchema
|
|
1104
|
+
location.schema = mergedSchema
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1143
1107
|
const type = schema.type
|
|
1144
1108
|
const nullable = schema.nullable === true
|
|
1145
1109
|
|
|
1146
|
-
|
|
1110
|
+
let code = ''
|
|
1111
|
+
let funcName
|
|
1147
1112
|
|
|
1148
1113
|
switch (type) {
|
|
1149
1114
|
case 'null':
|
|
1150
1115
|
code += `
|
|
1151
|
-
json +=
|
|
1116
|
+
json += serializer.asNull()
|
|
1152
1117
|
`
|
|
1153
1118
|
break
|
|
1154
1119
|
case 'string': {
|
|
1155
|
-
|
|
1156
|
-
code +=
|
|
1120
|
+
funcName = getStringSerializer(schema.format, nullable)
|
|
1121
|
+
code += `json += ${funcName}(${input})`
|
|
1157
1122
|
break
|
|
1158
1123
|
}
|
|
1159
1124
|
case 'integer':
|
|
1160
|
-
|
|
1125
|
+
funcName = nullable ? 'serializer.asIntegerNullable.bind(serializer)' : 'serializer.asInteger.bind(serializer)'
|
|
1126
|
+
code += `json += ${funcName}(${input})`
|
|
1161
1127
|
break
|
|
1162
1128
|
case 'number':
|
|
1163
|
-
|
|
1129
|
+
funcName = nullable ? 'serializer.asNumberNullable.bind(serializer)' : 'serializer.asNumber.bind(serializer)'
|
|
1130
|
+
code += `json += ${funcName}(${input})`
|
|
1164
1131
|
break
|
|
1165
1132
|
case 'boolean':
|
|
1166
|
-
|
|
1133
|
+
funcName = nullable ? 'serializer.asBooleanNullable.bind(serializer)' : 'serializer.asBoolean.bind(serializer)'
|
|
1134
|
+
code += `json += ${funcName}(${input})`
|
|
1167
1135
|
break
|
|
1168
1136
|
case 'object':
|
|
1169
|
-
funcName =
|
|
1170
|
-
laterCode = buildObject(location, laterCode, funcName)
|
|
1171
|
-
code += `
|
|
1172
|
-
json += ${funcName}(obj${accessor})
|
|
1173
|
-
`
|
|
1137
|
+
funcName = generateFuncName()
|
|
1138
|
+
laterCode = buildObject(location, laterCode, funcName, locationPath)
|
|
1139
|
+
code += `json += ${funcName}(${input})`
|
|
1174
1140
|
break
|
|
1175
1141
|
case 'array':
|
|
1176
|
-
funcName =
|
|
1177
|
-
laterCode = buildArray(location, laterCode, funcName,
|
|
1178
|
-
code += `
|
|
1179
|
-
json += ${funcName}(obj${accessor})
|
|
1180
|
-
`
|
|
1142
|
+
funcName = generateFuncName()
|
|
1143
|
+
laterCode = buildArray(location, laterCode, funcName, locationPath)
|
|
1144
|
+
code += `json += ${funcName}(${input})`
|
|
1181
1145
|
break
|
|
1182
1146
|
case undefined:
|
|
1183
|
-
if (
|
|
1147
|
+
if (schema.anyOf || schema.oneOf) {
|
|
1184
1148
|
// beware: dereferenceOfRefs has side effects and changes schema.anyOf
|
|
1185
|
-
const
|
|
1186
|
-
|
|
1187
|
-
const nestedResult =
|
|
1188
|
-
// We need a test serializer as the String serializer will not work with
|
|
1189
|
-
// date/time ajv validations
|
|
1190
|
-
// see: https://github.com/fastify/fast-json-stringify/issues/325
|
|
1191
|
-
const testSerializer = getTestSerializer(location.schema.format)
|
|
1192
|
-
const testValue = testSerializer !== undefined ? `${testSerializer}(obj${accessor}, true)` : `obj${accessor}`
|
|
1193
|
-
|
|
1149
|
+
const locations = dereferenceOfRefs(location, schema.anyOf ? 'anyOf' : 'oneOf')
|
|
1150
|
+
locations.forEach((location, index) => {
|
|
1151
|
+
const nestedResult = buildValue(laterCode, locationPath + 'i' + index, input, location)
|
|
1194
1152
|
// Since we are only passing the relevant schema to ajv.validate, it needs to be full dereferenced
|
|
1195
1153
|
// otherwise any $ref pointing to an external schema would result in an error.
|
|
1196
1154
|
// Full dereference of the schema happens as side effect of two functions:
|
|
1197
1155
|
// 1. `dereferenceOfRefs` loops through the `schema.anyOf`` array and replaces any top level reference
|
|
1198
1156
|
// with the actual schema
|
|
1199
|
-
// 2. `
|
|
1157
|
+
// 2. `buildValue`, through `buildCode`, replaces any reference in object properties with the actual schema
|
|
1200
1158
|
// (see https://github.com/fastify/fast-json-stringify/blob/6da3b3e8ac24b1ca5578223adedb4083b7adf8db/index.js#L631)
|
|
1201
1159
|
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
${nestedResult.code}
|
|
1208
|
-
`
|
|
1209
|
-
laterCode = nestedResult.laterCode
|
|
1210
|
-
})
|
|
1211
|
-
code += `
|
|
1212
|
-
else json+= null
|
|
1213
|
-
`
|
|
1214
|
-
} else if ('oneOf' in schema) {
|
|
1215
|
-
// beware: dereferenceOfRefs has side effects and changes schema.oneOf
|
|
1216
|
-
const oneOfLocations = dereferenceOfRefs(location, 'oneOf')
|
|
1217
|
-
oneOfLocations.forEach((location, index) => {
|
|
1218
|
-
const nestedResult = nested(laterCode, name, key, location, subKey !== '' ? subKey : 'i' + index, isArray)
|
|
1219
|
-
const testSerializer = getTestSerializer(location.schema.format)
|
|
1220
|
-
const testValue = testSerializer !== undefined ? `${testSerializer}(obj${accessor}, true)` : `obj${accessor}`
|
|
1221
|
-
// see comment on anyOf about dereferencing the schema before calling ajv.validate
|
|
1160
|
+
// Ajv does not support js date format. In order to properly validate objects containing a date,
|
|
1161
|
+
// it needs to replace all occurrences of the string date format with a custom keyword fjs_date_type.
|
|
1162
|
+
// (see https://github.com/fastify/fast-json-stringify/pull/441)
|
|
1163
|
+
const extendedSchema = clone(location.schema)
|
|
1164
|
+
extendDateTimeType(extendedSchema)
|
|
1222
1165
|
|
|
1223
1166
|
const schemaKey = location.schema.$id || randomUUID()
|
|
1224
|
-
ajvInstance.addSchema(
|
|
1167
|
+
ajvInstance.addSchema(extendedSchema, schemaKey)
|
|
1225
1168
|
|
|
1226
1169
|
code += `
|
|
1227
|
-
${index === 0 ? 'if' : 'else if'}(ajv.validate("${schemaKey}", ${
|
|
1170
|
+
${index === 0 ? 'if' : 'else if'}(ajv.validate("${schemaKey}", ${input}))
|
|
1228
1171
|
${nestedResult.code}
|
|
1229
1172
|
`
|
|
1230
1173
|
laterCode = nestedResult.laterCode
|
|
1231
1174
|
})
|
|
1232
1175
|
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
`
|
|
1237
|
-
}
|
|
1176
|
+
code += `
|
|
1177
|
+
else throw new Error(\`The value $\{JSON.stringify(${input})} does not match schema definition.\`)
|
|
1178
|
+
`
|
|
1238
1179
|
} else if (isEmpty(schema)) {
|
|
1239
1180
|
code += `
|
|
1240
|
-
json += JSON.stringify(
|
|
1181
|
+
json += JSON.stringify(${input})
|
|
1241
1182
|
`
|
|
1242
1183
|
} else if ('const' in schema) {
|
|
1243
1184
|
code += `
|
|
1244
|
-
if(ajv.validate(${JSON.stringify(schema)},
|
|
1185
|
+
if(ajv.validate(${JSON.stringify(schema)}, ${input}))
|
|
1245
1186
|
json += '${JSON.stringify(schema.const)}'
|
|
1246
1187
|
else
|
|
1247
|
-
throw new Error(\`Item $\{JSON.stringify(
|
|
1188
|
+
throw new Error(\`Item $\{JSON.stringify(${input})} does not match schema definition.\`)
|
|
1248
1189
|
`
|
|
1249
1190
|
} else if (schema.type === undefined) {
|
|
1250
1191
|
code += `
|
|
1251
|
-
json += JSON.stringify(
|
|
1192
|
+
json += JSON.stringify(${input})
|
|
1252
1193
|
`
|
|
1253
1194
|
} else {
|
|
1254
1195
|
throw new Error(`${schema.type} unsupported`)
|
|
@@ -1256,51 +1197,46 @@ function nested (laterCode, name, key, location, subKey, isArray) {
|
|
|
1256
1197
|
break
|
|
1257
1198
|
default:
|
|
1258
1199
|
if (Array.isArray(type)) {
|
|
1259
|
-
|
|
1260
|
-
const
|
|
1200
|
+
let sortedTypes = type
|
|
1201
|
+
const nullable = schema.nullable === true || type.includes('null')
|
|
1202
|
+
|
|
1203
|
+
if (nullable) {
|
|
1204
|
+
sortedTypes = sortedTypes.filter(type => type !== 'null')
|
|
1205
|
+
code += `
|
|
1206
|
+
if (${input} === null) {
|
|
1207
|
+
json += null
|
|
1208
|
+
} else {`
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1261
1211
|
sortedTypes.forEach((type, index) => {
|
|
1262
1212
|
const statement = index === 0 ? 'if' : 'else if'
|
|
1263
1213
|
const tempSchema = Object.assign({}, schema, { type })
|
|
1264
|
-
const nestedResult =
|
|
1214
|
+
const nestedResult = buildValue(laterCode, locationPath, input, mergeLocation(location, { schema: tempSchema }))
|
|
1265
1215
|
switch (type) {
|
|
1266
1216
|
case 'string': {
|
|
1267
1217
|
code += `
|
|
1268
|
-
${statement}(
|
|
1269
|
-
${nestedResult.code}
|
|
1270
|
-
`
|
|
1271
|
-
break
|
|
1272
|
-
}
|
|
1273
|
-
case 'null': {
|
|
1274
|
-
code += `
|
|
1275
|
-
${statement}(obj${accessor} == null)
|
|
1218
|
+
${statement}(${input} === null || typeof ${input} === "${type}" || ${input} instanceof Date || ${input} instanceof RegExp || (typeof ${input} === "object" && Object.hasOwnProperty.call(${input}, "toString")))
|
|
1276
1219
|
${nestedResult.code}
|
|
1277
1220
|
`
|
|
1278
1221
|
break
|
|
1279
1222
|
}
|
|
1280
1223
|
case 'array': {
|
|
1281
1224
|
code += `
|
|
1282
|
-
${statement}(Array.isArray(
|
|
1225
|
+
${statement}(Array.isArray(${input}))
|
|
1283
1226
|
${nestedResult.code}
|
|
1284
1227
|
`
|
|
1285
1228
|
break
|
|
1286
1229
|
}
|
|
1287
1230
|
case 'integer': {
|
|
1288
1231
|
code += `
|
|
1289
|
-
${statement}(Number.isInteger(
|
|
1290
|
-
${nestedResult.code}
|
|
1291
|
-
`
|
|
1292
|
-
break
|
|
1293
|
-
}
|
|
1294
|
-
case 'number': {
|
|
1295
|
-
code += `
|
|
1296
|
-
${statement}(isNaN(obj${accessor}) === false)
|
|
1232
|
+
${statement}(Number.isInteger(${input}) || ${input} === null)
|
|
1297
1233
|
${nestedResult.code}
|
|
1298
1234
|
`
|
|
1299
1235
|
break
|
|
1300
1236
|
}
|
|
1301
1237
|
default: {
|
|
1302
1238
|
code += `
|
|
1303
|
-
${statement}(typeof
|
|
1239
|
+
${statement}(typeof ${input} === "${type}" || ${input} === null)
|
|
1304
1240
|
${nestedResult.code}
|
|
1305
1241
|
`
|
|
1306
1242
|
break
|
|
@@ -1309,16 +1245,32 @@ function nested (laterCode, name, key, location, subKey, isArray) {
|
|
|
1309
1245
|
laterCode = nestedResult.laterCode
|
|
1310
1246
|
})
|
|
1311
1247
|
code += `
|
|
1312
|
-
else
|
|
1248
|
+
else throw new Error(\`The value $\{JSON.stringify(${input})} does not match schema definition.\`)
|
|
1313
1249
|
`
|
|
1250
|
+
|
|
1251
|
+
if (nullable) {
|
|
1252
|
+
code += `
|
|
1253
|
+
}
|
|
1254
|
+
`
|
|
1255
|
+
}
|
|
1314
1256
|
} else {
|
|
1315
1257
|
throw new Error(`${type} unsupported`)
|
|
1316
1258
|
}
|
|
1317
1259
|
}
|
|
1318
1260
|
|
|
1319
|
-
return {
|
|
1320
|
-
|
|
1321
|
-
|
|
1261
|
+
return { code, laterCode }
|
|
1262
|
+
}
|
|
1263
|
+
|
|
1264
|
+
function extendDateTimeType (schema) {
|
|
1265
|
+
if (schema.type === 'string' && ['date-time', 'date', 'time'].includes(schema.format)) {
|
|
1266
|
+
schema.fjs_date_type = schema.format
|
|
1267
|
+
delete schema.type
|
|
1268
|
+
delete schema.format
|
|
1269
|
+
}
|
|
1270
|
+
for (const property in schema) {
|
|
1271
|
+
if (typeof schema[property] === 'object') {
|
|
1272
|
+
extendDateTimeType(schema[property])
|
|
1273
|
+
}
|
|
1322
1274
|
}
|
|
1323
1275
|
}
|
|
1324
1276
|
|
|
@@ -1334,8 +1286,11 @@ function isEmpty (schema) {
|
|
|
1334
1286
|
|
|
1335
1287
|
module.exports = build
|
|
1336
1288
|
|
|
1289
|
+
module.exports.validLargeArrayMechanisms = validLargeArrayMechanisms
|
|
1290
|
+
|
|
1337
1291
|
module.exports.restore = function ({ code, ajv }) {
|
|
1292
|
+
const serializer = new Serializer()
|
|
1338
1293
|
// eslint-disable-next-line
|
|
1339
|
-
return (Function.apply(null, ['ajv', code])
|
|
1340
|
-
.apply(null, [ajv]))
|
|
1294
|
+
return (Function.apply(null, ['ajv', 'serializer', code])
|
|
1295
|
+
.apply(null, [ajv, serializer]))
|
|
1341
1296
|
}
|