fast-json-stringify 4.0.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/README.md +31 -5
- package/ajv.js +37 -0
- package/index.js +159 -347
- package/package.json +1 -1
- package/serializer.js +168 -0
- package/test/array.test.js +29 -0
- package/test/fixtures/.keep +0 -0
- package/test/standalone-mode.test.js +38 -0
package/README.md
CHANGED
|
@@ -62,6 +62,8 @@ compile-json-stringify date format x 1,086,187 ops/sec ±0.16% (99 runs sampled)
|
|
|
62
62
|
- <a href="#nullable">`Nullable`</a>
|
|
63
63
|
- <a href="#largearrays">`Large Arrays`</a>
|
|
64
64
|
- <a href="#security">`Security Notice`</a>
|
|
65
|
+
- <a href="#debug">`Debug Mode`</a>
|
|
66
|
+
- <a href="#standalone">`Standalone Mode`</a>
|
|
65
67
|
- <a href="#acknowledgements">`Acknowledgements`</a>
|
|
66
68
|
- <a href="#license">`License`</a>
|
|
67
69
|
|
|
@@ -663,13 +665,37 @@ const debugCompiled = fastJson({
|
|
|
663
665
|
type: 'string'
|
|
664
666
|
}
|
|
665
667
|
}
|
|
666
|
-
}, {
|
|
668
|
+
}, { mode: 'debug' })
|
|
667
669
|
|
|
668
|
-
console.log(debugCompiled) // it is
|
|
669
|
-
|
|
670
|
+
console.log(debugCompiled) // it is a object contain code, ajv instance
|
|
671
|
+
const rawString = debugCompiled.code // it is the generated code
|
|
672
|
+
console.log(rawString)
|
|
670
673
|
|
|
671
|
-
const
|
|
672
|
-
|
|
674
|
+
const stringify = fastJson.restore(debugCompiled) // use the generated string to get back the `stringify` function
|
|
675
|
+
console.log(stringify({ firstName: 'Foo', surname: 'bar' })) // '{"firstName":"Foo"}'
|
|
676
|
+
```
|
|
677
|
+
|
|
678
|
+
<a name="standalone"></a>
|
|
679
|
+
### Standalone Mode
|
|
680
|
+
|
|
681
|
+
The standalone mode is used to compile the code that can be directly run by `node`
|
|
682
|
+
itself. You need to install `fast-json-stringify`, `ajv`, `fast-uri` and `ajv-formats`
|
|
683
|
+
in order to let the standalone code works.
|
|
684
|
+
|
|
685
|
+
```js
|
|
686
|
+
const fs = require('fs')
|
|
687
|
+
const code = fastJson({
|
|
688
|
+
title: 'default string',
|
|
689
|
+
type: 'object',
|
|
690
|
+
properties: {
|
|
691
|
+
firstName: {
|
|
692
|
+
type: 'string'
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
}, { mode: 'standalone' })
|
|
696
|
+
|
|
697
|
+
fs.writeFileSync('stringify.js', code)
|
|
698
|
+
const stringify = require('stringify.js')
|
|
673
699
|
console.log(stringify({ firstName: 'Foo', surname: 'bar' })) // '{"firstName":"Foo"}'
|
|
674
700
|
```
|
|
675
701
|
|
package/ajv.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const Ajv = require('ajv')
|
|
4
|
+
const fastUri = require('fast-uri')
|
|
5
|
+
const ajvFormats = require('ajv-formats')
|
|
6
|
+
|
|
7
|
+
module.exports = buildAjv
|
|
8
|
+
|
|
9
|
+
function buildAjv (options) {
|
|
10
|
+
const ajvInstance = new Ajv({ ...options, strictSchema: false, uriResolver: fastUri })
|
|
11
|
+
ajvFormats(ajvInstance)
|
|
12
|
+
|
|
13
|
+
const validateDateTimeFormat = ajvFormats.get('date-time').validate
|
|
14
|
+
const validateDateFormat = ajvFormats.get('date').validate
|
|
15
|
+
const validateTimeFormat = ajvFormats.get('time').validate
|
|
16
|
+
|
|
17
|
+
ajvInstance.addKeyword({
|
|
18
|
+
keyword: 'fjs_date_type',
|
|
19
|
+
validate: (schema, date) => {
|
|
20
|
+
if (date instanceof Date) {
|
|
21
|
+
return true
|
|
22
|
+
}
|
|
23
|
+
if (schema === 'date-time') {
|
|
24
|
+
return validateDateTimeFormat(date)
|
|
25
|
+
}
|
|
26
|
+
if (schema === 'date') {
|
|
27
|
+
return validateDateFormat(date)
|
|
28
|
+
}
|
|
29
|
+
if (schema === 'time') {
|
|
30
|
+
return validateTimeFormat(date)
|
|
31
|
+
}
|
|
32
|
+
return false
|
|
33
|
+
}
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
return ajvInstance
|
|
37
|
+
}
|
package/index.js
CHANGED
|
@@ -2,15 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
/* eslint no-prototype-builtins: 0 */
|
|
4
4
|
|
|
5
|
-
const Ajv = require('ajv')
|
|
6
|
-
const fastUri = require('fast-uri')
|
|
7
|
-
const ajvFormats = require('ajv-formats')
|
|
8
5
|
const merge = require('deepmerge')
|
|
9
6
|
const clone = require('rfdc')({ proto: true })
|
|
10
7
|
const fjsCloned = Symbol('fast-json-stringify.cloned')
|
|
11
8
|
const { randomUUID } = require('crypto')
|
|
12
9
|
|
|
13
10
|
const validate = require('./schema-validator')
|
|
11
|
+
const Serializer = require('./serializer')
|
|
12
|
+
const buildAjv = require('./ajv')
|
|
14
13
|
|
|
15
14
|
let largeArraySize = 2e4
|
|
16
15
|
let stringSimilarity = null
|
|
@@ -55,206 +54,17 @@ const objectReferenceSerializersMap = new Map()
|
|
|
55
54
|
const schemaReferenceMap = new Map()
|
|
56
55
|
|
|
57
56
|
let ajvInstance = null
|
|
58
|
-
|
|
59
|
-
class Serializer {
|
|
60
|
-
constructor (options = {}) {
|
|
61
|
-
switch (options.rounding) {
|
|
62
|
-
case 'floor':
|
|
63
|
-
this.parseInteger = Math.floor
|
|
64
|
-
break
|
|
65
|
-
case 'ceil':
|
|
66
|
-
this.parseInteger = Math.ceil
|
|
67
|
-
break
|
|
68
|
-
case 'round':
|
|
69
|
-
this.parseInteger = Math.round
|
|
70
|
-
break
|
|
71
|
-
default:
|
|
72
|
-
this.parseInteger = Math.trunc
|
|
73
|
-
break
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
asAny (i) {
|
|
78
|
-
return JSON.stringify(i)
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
asNull () {
|
|
82
|
-
return 'null'
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
asInteger (i) {
|
|
86
|
-
if (typeof i === 'bigint') {
|
|
87
|
-
return i.toString()
|
|
88
|
-
} else if (Number.isInteger(i)) {
|
|
89
|
-
return '' + i
|
|
90
|
-
} else {
|
|
91
|
-
/* eslint no-undef: "off" */
|
|
92
|
-
const integer = this.parseInteger(i)
|
|
93
|
-
if (Number.isNaN(integer)) {
|
|
94
|
-
throw new Error(`The value "${i}" cannot be converted to an integer.`)
|
|
95
|
-
} else {
|
|
96
|
-
return '' + integer
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
asIntegerNullable (i) {
|
|
102
|
-
return i === null ? 'null' : this.asInteger(i)
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
asNumber (i) {
|
|
106
|
-
const num = Number(i)
|
|
107
|
-
if (Number.isNaN(num)) {
|
|
108
|
-
throw new Error(`The value "${i}" cannot be converted to a number.`)
|
|
109
|
-
} else {
|
|
110
|
-
return '' + num
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
asNumberNullable (i) {
|
|
115
|
-
return i === null ? 'null' : this.asNumber(i)
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
asBoolean (bool) {
|
|
119
|
-
return bool && 'true' || 'false' // eslint-disable-line
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
asBooleanNullable (bool) {
|
|
123
|
-
return bool === null ? 'null' : this.asBoolean(bool)
|
|
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
|
-
}
|
|
57
|
+
let contextFunctions = null
|
|
225
58
|
|
|
226
59
|
function build (schema, options) {
|
|
227
60
|
arrayItemsReferenceSerializersMap.clear()
|
|
228
61
|
objectReferenceSerializersMap.clear()
|
|
229
62
|
schemaReferenceMap.clear()
|
|
230
63
|
|
|
64
|
+
contextFunctions = []
|
|
231
65
|
options = options || {}
|
|
232
66
|
|
|
233
|
-
ajvInstance =
|
|
234
|
-
ajvFormats(ajvInstance)
|
|
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
|
-
})
|
|
67
|
+
ajvInstance = buildAjv(options.ajv)
|
|
258
68
|
|
|
259
69
|
isValidSchema(schema)
|
|
260
70
|
if (options.schema) {
|
|
@@ -303,29 +113,50 @@ function build (schema, options) {
|
|
|
303
113
|
schema.type = inferTypeByKeyword(schema)
|
|
304
114
|
}
|
|
305
115
|
|
|
306
|
-
const
|
|
116
|
+
const code = buildValue('main', 'input', location)
|
|
117
|
+
|
|
307
118
|
const contextFunctionCode = `
|
|
308
|
-
'use strict'
|
|
309
119
|
function main (input) {
|
|
310
120
|
let json = ''
|
|
311
121
|
${code}
|
|
312
122
|
return json
|
|
313
123
|
}
|
|
314
|
-
${
|
|
124
|
+
${contextFunctions.join('\n')}
|
|
315
125
|
return main
|
|
316
126
|
`
|
|
317
127
|
|
|
318
128
|
const dependenciesName = ['ajv', 'serializer', contextFunctionCode]
|
|
319
129
|
|
|
320
130
|
if (options.debugMode) {
|
|
131
|
+
options.mode = 'debug'
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (options.mode === 'debug') {
|
|
321
135
|
return { code: dependenciesName.join('\n'), ajv: ajvInstance }
|
|
322
136
|
}
|
|
323
137
|
|
|
138
|
+
if (options.mode === 'standalone') {
|
|
139
|
+
return `
|
|
140
|
+
'use strict'
|
|
141
|
+
|
|
142
|
+
const Serializer = require('fast-json-stringify/serializer')
|
|
143
|
+
const buildAjv = require('fast-json-stringify/ajv')
|
|
144
|
+
|
|
145
|
+
const serializer = new Serializer(${JSON.stringify(options || {})})
|
|
146
|
+
const ajv = buildAjv(${JSON.stringify(options.ajv || {})})
|
|
147
|
+
|
|
148
|
+
${contextFunctionCode.replace('return main', '')}
|
|
149
|
+
|
|
150
|
+
module.exports = main
|
|
151
|
+
`
|
|
152
|
+
}
|
|
153
|
+
|
|
324
154
|
/* eslint no-new-func: "off" */
|
|
325
155
|
const contextFunc = new Function('ajv', 'serializer', contextFunctionCode)
|
|
326
156
|
const stringifyFunc = contextFunc(ajvInstance, serializer)
|
|
327
157
|
|
|
328
158
|
ajvInstance = null
|
|
159
|
+
contextFunctions = null
|
|
329
160
|
arrayItemsReferenceSerializersMap.clear()
|
|
330
161
|
objectReferenceSerializersMap.clear()
|
|
331
162
|
schemaReferenceMap.clear()
|
|
@@ -408,7 +239,6 @@ function addPatternProperties (location) {
|
|
|
408
239
|
for (var i = 0; i < keys.length; i++) {
|
|
409
240
|
if (properties[keys[i]]) continue
|
|
410
241
|
`
|
|
411
|
-
let laterCode = ''
|
|
412
242
|
|
|
413
243
|
Object.keys(pp).forEach((regex, index) => {
|
|
414
244
|
let ppLocation = mergeLocation(location, { schema: pp[regex] })
|
|
@@ -423,27 +253,24 @@ function addPatternProperties (location) {
|
|
|
423
253
|
throw new Error(`${err.message}. Found at ${regex} matching ${JSON.stringify(pp[regex])}`)
|
|
424
254
|
}
|
|
425
255
|
|
|
426
|
-
const valueCode = buildValue('', '
|
|
427
|
-
laterCode += valueCode.laterCode
|
|
256
|
+
const valueCode = buildValue('', 'obj[keys[i]]', ppLocation)
|
|
428
257
|
code += `
|
|
429
258
|
if (/${regex.replace(/\\*\//g, '\\/')}/.test(keys[i])) {
|
|
430
259
|
${addComma}
|
|
431
260
|
json += serializer.asString(keys[i]) + ':'
|
|
432
|
-
${valueCode
|
|
261
|
+
${valueCode}
|
|
433
262
|
continue
|
|
434
263
|
}
|
|
435
264
|
`
|
|
436
265
|
})
|
|
437
266
|
if (schema.additionalProperties) {
|
|
438
|
-
|
|
439
|
-
code += additionalPropertyCode.code
|
|
440
|
-
laterCode += additionalPropertyCode.laterCode
|
|
267
|
+
code += additionalProperty(location)
|
|
441
268
|
}
|
|
442
269
|
|
|
443
270
|
code += `
|
|
444
271
|
}
|
|
445
272
|
`
|
|
446
|
-
return
|
|
273
|
+
return code
|
|
447
274
|
}
|
|
448
275
|
|
|
449
276
|
function additionalProperty (location) {
|
|
@@ -457,7 +284,7 @@ function additionalProperty (location) {
|
|
|
457
284
|
}
|
|
458
285
|
`
|
|
459
286
|
|
|
460
|
-
return
|
|
287
|
+
return code
|
|
461
288
|
}
|
|
462
289
|
let apLocation = mergeLocation(location, { schema: ap })
|
|
463
290
|
if (ap.$ref) {
|
|
@@ -465,29 +292,26 @@ function additionalProperty (location) {
|
|
|
465
292
|
ap = apLocation.schema
|
|
466
293
|
}
|
|
467
294
|
|
|
468
|
-
const valueCode = buildValue('', '
|
|
295
|
+
const valueCode = buildValue('', 'obj[keys[i]]', apLocation)
|
|
469
296
|
|
|
470
297
|
code += `
|
|
471
298
|
${addComma}
|
|
472
299
|
json += serializer.asString(keys[i]) + ':'
|
|
473
|
-
${valueCode
|
|
300
|
+
${valueCode}
|
|
474
301
|
`
|
|
475
302
|
|
|
476
|
-
return
|
|
303
|
+
return code
|
|
477
304
|
}
|
|
478
305
|
|
|
479
306
|
function addAdditionalProperties (location) {
|
|
480
|
-
|
|
481
|
-
const code = `
|
|
307
|
+
return `
|
|
482
308
|
var properties = ${JSON.stringify(location.schema.properties)} || {}
|
|
483
309
|
var keys = Object.keys(obj)
|
|
484
310
|
for (var i = 0; i < keys.length; i++) {
|
|
485
311
|
if (properties[keys[i]]) continue
|
|
486
|
-
${
|
|
312
|
+
${additionalProperty(location)}
|
|
487
313
|
}
|
|
488
314
|
`
|
|
489
|
-
|
|
490
|
-
return { code, laterCode: additionalPropertyCode.laterCode }
|
|
491
315
|
}
|
|
492
316
|
|
|
493
317
|
function idFinder (schema, searchedId) {
|
|
@@ -621,7 +445,7 @@ function refFinder (ref, location) {
|
|
|
621
445
|
}
|
|
622
446
|
}
|
|
623
447
|
|
|
624
|
-
function buildCode (location,
|
|
448
|
+
function buildCode (location, locationPath) {
|
|
625
449
|
if (location.schema.$ref) {
|
|
626
450
|
location = refFinder(location.schema.$ref, location)
|
|
627
451
|
}
|
|
@@ -629,6 +453,8 @@ function buildCode (location, code, laterCode, locationPath) {
|
|
|
629
453
|
const schema = location.schema
|
|
630
454
|
const required = schema.required || []
|
|
631
455
|
|
|
456
|
+
let code = ''
|
|
457
|
+
|
|
632
458
|
Object.keys(schema.properties || {}).forEach((key) => {
|
|
633
459
|
let propertyLocation = mergeLocation(location, { schema: schema.properties[key] })
|
|
634
460
|
if (schema.properties[key].$ref) {
|
|
@@ -648,9 +474,7 @@ function buildCode (location, code, laterCode, locationPath) {
|
|
|
648
474
|
json += ${asString} + ':'
|
|
649
475
|
`
|
|
650
476
|
|
|
651
|
-
|
|
652
|
-
code += result.code
|
|
653
|
-
laterCode = result.laterCode
|
|
477
|
+
code += buildValue(locationPath + key, `obj[${JSON.stringify(key)}]`, mergeLocation(propertyLocation, { schema: schema.properties[key] }))
|
|
654
478
|
|
|
655
479
|
const defaultValue = schema.properties[key].default
|
|
656
480
|
if (defaultValue !== undefined) {
|
|
@@ -676,7 +500,7 @@ function buildCode (location, code, laterCode, locationPath) {
|
|
|
676
500
|
code += `if (obj['${requiredProperty}'] === undefined) throw new Error('"${requiredProperty}" is required!')\n`
|
|
677
501
|
}
|
|
678
502
|
|
|
679
|
-
return
|
|
503
|
+
return code
|
|
680
504
|
}
|
|
681
505
|
|
|
682
506
|
function mergeAllOfSchema (location, schema, mergedSchema) {
|
|
@@ -771,24 +595,17 @@ function mergeAllOfSchema (location, schema, mergedSchema) {
|
|
|
771
595
|
|
|
772
596
|
function buildInnerObject (location, locationPath) {
|
|
773
597
|
const schema = location.schema
|
|
774
|
-
|
|
598
|
+
let code = buildCode(location, locationPath)
|
|
775
599
|
if (schema.patternProperties) {
|
|
776
|
-
|
|
777
|
-
result.code += code
|
|
778
|
-
result.laterCode += laterCode
|
|
600
|
+
code += addPatternProperties(location)
|
|
779
601
|
} else if (schema.additionalProperties && !schema.patternProperties) {
|
|
780
|
-
|
|
781
|
-
result.code += code
|
|
782
|
-
result.laterCode += laterCode
|
|
602
|
+
code += addAdditionalProperties(location)
|
|
783
603
|
}
|
|
784
|
-
return
|
|
604
|
+
return code
|
|
785
605
|
}
|
|
786
606
|
|
|
787
607
|
function addIfThenElse (location, locationPath) {
|
|
788
608
|
let code = ''
|
|
789
|
-
let r
|
|
790
|
-
let laterCode = ''
|
|
791
|
-
let innerR
|
|
792
609
|
|
|
793
610
|
const schema = location.schema
|
|
794
611
|
const copy = merge({}, schema)
|
|
@@ -809,14 +626,10 @@ function addIfThenElse (location, locationPath) {
|
|
|
809
626
|
if (valid) {
|
|
810
627
|
`
|
|
811
628
|
if (merged.if && merged.then) {
|
|
812
|
-
|
|
813
|
-
code += innerR.code
|
|
814
|
-
laterCode = innerR.laterCode
|
|
629
|
+
code += addIfThenElse(mergedLocation, locationPath + 'Then')
|
|
815
630
|
}
|
|
816
631
|
|
|
817
|
-
|
|
818
|
-
code += r.code
|
|
819
|
-
laterCode += r.laterCode
|
|
632
|
+
code += buildInnerObject(mergedLocation, locationPath + 'Then')
|
|
820
633
|
|
|
821
634
|
code += `
|
|
822
635
|
}
|
|
@@ -829,19 +642,15 @@ function addIfThenElse (location, locationPath) {
|
|
|
829
642
|
`
|
|
830
643
|
|
|
831
644
|
if (merged.if && merged.then) {
|
|
832
|
-
|
|
833
|
-
code += innerR.code
|
|
834
|
-
laterCode += innerR.laterCode
|
|
645
|
+
code += addIfThenElse(mergedLocation, locationPath + 'Else')
|
|
835
646
|
}
|
|
836
647
|
|
|
837
|
-
|
|
838
|
-
code += r.code
|
|
839
|
-
laterCode += r.laterCode
|
|
648
|
+
code += buildInnerObject(mergedLocation, locationPath + 'Else')
|
|
840
649
|
|
|
841
650
|
code += `
|
|
842
651
|
}
|
|
843
652
|
`
|
|
844
|
-
return
|
|
653
|
+
return code
|
|
845
654
|
}
|
|
846
655
|
|
|
847
656
|
function toJSON (variableName) {
|
|
@@ -851,76 +660,63 @@ function toJSON (variableName) {
|
|
|
851
660
|
`
|
|
852
661
|
}
|
|
853
662
|
|
|
854
|
-
function buildObject (location,
|
|
663
|
+
function buildObject (location, locationPath) {
|
|
855
664
|
const schema = location.schema
|
|
856
665
|
if (schema.$id !== undefined) {
|
|
857
666
|
schemaReferenceMap.set(schema.$id, schema)
|
|
858
667
|
}
|
|
859
|
-
|
|
668
|
+
|
|
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 = `
|
|
860
677
|
function ${functionName} (input) {
|
|
861
678
|
// ${locationPath}
|
|
862
679
|
`
|
|
863
680
|
if (schema.nullable) {
|
|
864
|
-
|
|
865
|
-
if(input === null) {
|
|
681
|
+
functionCode += `
|
|
682
|
+
if (input === null) {
|
|
866
683
|
return 'null';
|
|
867
684
|
}
|
|
868
685
|
`
|
|
869
686
|
}
|
|
870
687
|
|
|
871
|
-
|
|
872
|
-
code += `
|
|
873
|
-
return ${objectReferenceSerializersMap.get(schema)}(input)
|
|
874
|
-
}
|
|
875
|
-
`
|
|
876
|
-
return code
|
|
877
|
-
}
|
|
878
|
-
objectReferenceSerializersMap.set(schema, functionName)
|
|
879
|
-
|
|
880
|
-
code += `
|
|
688
|
+
functionCode += `
|
|
881
689
|
var obj = ${toJSON('input')}
|
|
882
690
|
var json = '{'
|
|
883
691
|
var addComma = false
|
|
884
692
|
`
|
|
885
693
|
|
|
886
|
-
let
|
|
694
|
+
let rCode
|
|
887
695
|
if (schema.if && schema.then) {
|
|
888
|
-
|
|
696
|
+
functionCode += `
|
|
889
697
|
var valid
|
|
890
698
|
`
|
|
891
|
-
|
|
699
|
+
rCode = addIfThenElse(location, locationPath)
|
|
892
700
|
} else {
|
|
893
|
-
|
|
701
|
+
rCode = buildInnerObject(location, locationPath)
|
|
894
702
|
}
|
|
895
703
|
|
|
896
704
|
// Removes the comma if is the last element of the string (in case there are not properties)
|
|
897
|
-
|
|
705
|
+
functionCode += `${rCode}
|
|
898
706
|
json += '}'
|
|
899
707
|
return json
|
|
900
708
|
}
|
|
901
|
-
${r.laterCode}
|
|
902
709
|
`
|
|
903
710
|
|
|
904
|
-
|
|
711
|
+
contextFunctions.push(functionCode)
|
|
712
|
+
return functionName
|
|
905
713
|
}
|
|
906
714
|
|
|
907
|
-
function buildArray (location,
|
|
715
|
+
function buildArray (location, locationPath) {
|
|
908
716
|
let schema = location.schema
|
|
909
717
|
if (schema.$id !== undefined) {
|
|
910
718
|
schemaReferenceMap.set(schema.$id, schema)
|
|
911
719
|
}
|
|
912
|
-
code += `
|
|
913
|
-
function ${functionName} (obj) {
|
|
914
|
-
// ${locationPath}
|
|
915
|
-
`
|
|
916
|
-
if (schema.nullable) {
|
|
917
|
-
code += `
|
|
918
|
-
if(obj === null) {
|
|
919
|
-
return 'null';
|
|
920
|
-
}
|
|
921
|
-
`
|
|
922
|
-
}
|
|
923
|
-
const laterCode = ''
|
|
924
720
|
|
|
925
721
|
// default to any items type
|
|
926
722
|
if (!schema.items) {
|
|
@@ -936,85 +732,105 @@ function buildArray (location, code, functionName, locationPath) {
|
|
|
936
732
|
|
|
937
733
|
location = refFinder(schema.items.$ref, location)
|
|
938
734
|
schema.items = location.schema
|
|
735
|
+
}
|
|
939
736
|
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
return ${arrayItemsReferenceSerializersMap.get(schema.items)}(obj)
|
|
943
|
-
}
|
|
944
|
-
`
|
|
945
|
-
return code
|
|
946
|
-
}
|
|
947
|
-
arrayItemsReferenceSerializersMap.set(schema.items, functionName)
|
|
737
|
+
if (arrayItemsReferenceSerializersMap.has(schema.items)) {
|
|
738
|
+
return arrayItemsReferenceSerializersMap.get(schema.items)
|
|
948
739
|
}
|
|
949
740
|
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
if (Array.isArray(schema.items)) {
|
|
953
|
-
result = schema.items.reduce((res, item, i) => {
|
|
954
|
-
const tmpRes = buildValue(laterCode, locationPath + accessor + i, 'obj[i]', mergeLocation(location, { schema: item }))
|
|
955
|
-
const condition = `i === ${i} && ${buildArrayTypeCondition(item.type, accessor)}`
|
|
956
|
-
return {
|
|
957
|
-
code: `${res.code}
|
|
958
|
-
${i > 0 ? 'else' : ''} if (${condition}) {
|
|
959
|
-
${tmpRes.code}
|
|
960
|
-
}`,
|
|
961
|
-
laterCode: `${res.laterCode}
|
|
962
|
-
${tmpRes.laterCode}`
|
|
963
|
-
}
|
|
964
|
-
}, result)
|
|
741
|
+
const functionName = generateFuncName()
|
|
742
|
+
arrayItemsReferenceSerializersMap.set(schema.items, functionName)
|
|
965
743
|
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
${tmpRes.code}
|
|
971
|
-
}
|
|
972
|
-
`
|
|
973
|
-
}
|
|
744
|
+
let functionCode = `
|
|
745
|
+
function ${functionName} (obj) {
|
|
746
|
+
// ${locationPath}
|
|
747
|
+
`
|
|
974
748
|
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
749
|
+
if (schema.nullable) {
|
|
750
|
+
functionCode += `
|
|
751
|
+
if (obj === null) {
|
|
752
|
+
return 'null';
|
|
753
|
+
}
|
|
979
754
|
`
|
|
980
|
-
} else {
|
|
981
|
-
result = buildValue(laterCode, locationPath + accessor, 'obj[i]', mergeLocation(location, { schema: schema.items }))
|
|
982
755
|
}
|
|
983
756
|
|
|
984
|
-
|
|
757
|
+
functionCode += `
|
|
985
758
|
if (!Array.isArray(obj)) {
|
|
986
759
|
throw new TypeError(\`The value '$\{obj}' does not match schema definition.\`)
|
|
987
760
|
}
|
|
761
|
+
const arrayLength = obj.length
|
|
988
762
|
`
|
|
989
763
|
|
|
990
|
-
|
|
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
|
+
}
|
|
769
|
+
`
|
|
770
|
+
}
|
|
771
|
+
|
|
991
772
|
if (largeArrayMechanism !== 'default') {
|
|
992
773
|
if (largeArrayMechanism === 'json-stringify') {
|
|
993
|
-
|
|
774
|
+
functionCode += `if (arrayLength && arrayLength >= ${largeArraySize}) return JSON.stringify(obj)\n`
|
|
994
775
|
} else {
|
|
995
776
|
throw new Error(`Unsupported large array mechanism ${largeArrayMechanism}`)
|
|
996
777
|
}
|
|
997
778
|
}
|
|
998
779
|
|
|
999
|
-
|
|
1000
|
-
let jsonOutput= ''
|
|
1001
|
-
|
|
1002
|
-
let json = ''
|
|
1003
|
-
${result.code}
|
|
1004
|
-
jsonOutput += json
|
|
780
|
+
functionCode += `
|
|
781
|
+
let jsonOutput = ''
|
|
782
|
+
`
|
|
1005
783
|
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
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
|
+
`
|
|
803
|
+
}
|
|
804
|
+
|
|
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
|
+
}`
|
|
1009
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
|
+
}`
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
functionCode += `
|
|
1010
829
|
return \`[\${jsonOutput}]\`
|
|
1011
830
|
}`
|
|
1012
831
|
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
`
|
|
1016
|
-
|
|
1017
|
-
return code
|
|
832
|
+
contextFunctions.push(functionCode)
|
|
833
|
+
return functionName
|
|
1018
834
|
}
|
|
1019
835
|
|
|
1020
836
|
function buildArrayTypeCondition (type, accessor) {
|
|
@@ -1083,7 +899,7 @@ function generateFuncName () {
|
|
|
1083
899
|
return 'anonymous' + genFuncNameCounter++
|
|
1084
900
|
}
|
|
1085
901
|
|
|
1086
|
-
function buildValue (
|
|
902
|
+
function buildValue (locationPath, input, location) {
|
|
1087
903
|
let schema = location.schema
|
|
1088
904
|
|
|
1089
905
|
if (schema.$ref) {
|
|
@@ -1134,13 +950,11 @@ function buildValue (laterCode, locationPath, input, location) {
|
|
|
1134
950
|
code += `json += ${funcName}(${input})`
|
|
1135
951
|
break
|
|
1136
952
|
case 'object':
|
|
1137
|
-
funcName =
|
|
1138
|
-
laterCode = buildObject(location, laterCode, funcName, locationPath)
|
|
953
|
+
funcName = buildObject(location, locationPath)
|
|
1139
954
|
code += `json += ${funcName}(${input})`
|
|
1140
955
|
break
|
|
1141
956
|
case 'array':
|
|
1142
|
-
funcName =
|
|
1143
|
-
laterCode = buildArray(location, laterCode, funcName, locationPath)
|
|
957
|
+
funcName = buildArray(location, locationPath)
|
|
1144
958
|
code += `json += ${funcName}(${input})`
|
|
1145
959
|
break
|
|
1146
960
|
case undefined:
|
|
@@ -1148,7 +962,7 @@ function buildValue (laterCode, locationPath, input, location) {
|
|
|
1148
962
|
// beware: dereferenceOfRefs has side effects and changes schema.anyOf
|
|
1149
963
|
const locations = dereferenceOfRefs(location, schema.anyOf ? 'anyOf' : 'oneOf')
|
|
1150
964
|
locations.forEach((location, index) => {
|
|
1151
|
-
const nestedResult = buildValue(
|
|
965
|
+
const nestedResult = buildValue(locationPath + 'i' + index, input, location)
|
|
1152
966
|
// Since we are only passing the relevant schema to ajv.validate, it needs to be full dereferenced
|
|
1153
967
|
// otherwise any $ref pointing to an external schema would result in an error.
|
|
1154
968
|
// Full dereference of the schema happens as side effect of two functions:
|
|
@@ -1168,9 +982,8 @@ function buildValue (laterCode, locationPath, input, location) {
|
|
|
1168
982
|
|
|
1169
983
|
code += `
|
|
1170
984
|
${index === 0 ? 'if' : 'else if'}(ajv.validate("${schemaKey}", ${input}))
|
|
1171
|
-
${nestedResult
|
|
985
|
+
${nestedResult}
|
|
1172
986
|
`
|
|
1173
|
-
laterCode = nestedResult.laterCode
|
|
1174
987
|
})
|
|
1175
988
|
|
|
1176
989
|
code += `
|
|
@@ -1211,38 +1024,37 @@ function buildValue (laterCode, locationPath, input, location) {
|
|
|
1211
1024
|
sortedTypes.forEach((type, index) => {
|
|
1212
1025
|
const statement = index === 0 ? 'if' : 'else if'
|
|
1213
1026
|
const tempSchema = Object.assign({}, schema, { type })
|
|
1214
|
-
const nestedResult = buildValue(
|
|
1027
|
+
const nestedResult = buildValue(locationPath, input, mergeLocation(location, { schema: tempSchema }))
|
|
1215
1028
|
switch (type) {
|
|
1216
1029
|
case 'string': {
|
|
1217
1030
|
code += `
|
|
1218
1031
|
${statement}(${input} === null || typeof ${input} === "${type}" || ${input} instanceof Date || ${input} instanceof RegExp || (typeof ${input} === "object" && Object.hasOwnProperty.call(${input}, "toString")))
|
|
1219
|
-
${nestedResult
|
|
1032
|
+
${nestedResult}
|
|
1220
1033
|
`
|
|
1221
1034
|
break
|
|
1222
1035
|
}
|
|
1223
1036
|
case 'array': {
|
|
1224
1037
|
code += `
|
|
1225
1038
|
${statement}(Array.isArray(${input}))
|
|
1226
|
-
${nestedResult
|
|
1039
|
+
${nestedResult}
|
|
1227
1040
|
`
|
|
1228
1041
|
break
|
|
1229
1042
|
}
|
|
1230
1043
|
case 'integer': {
|
|
1231
1044
|
code += `
|
|
1232
1045
|
${statement}(Number.isInteger(${input}) || ${input} === null)
|
|
1233
|
-
${nestedResult
|
|
1046
|
+
${nestedResult}
|
|
1234
1047
|
`
|
|
1235
1048
|
break
|
|
1236
1049
|
}
|
|
1237
1050
|
default: {
|
|
1238
1051
|
code += `
|
|
1239
1052
|
${statement}(typeof ${input} === "${type}" || ${input} === null)
|
|
1240
|
-
${nestedResult
|
|
1053
|
+
${nestedResult}
|
|
1241
1054
|
`
|
|
1242
1055
|
break
|
|
1243
1056
|
}
|
|
1244
1057
|
}
|
|
1245
|
-
laterCode = nestedResult.laterCode
|
|
1246
1058
|
})
|
|
1247
1059
|
code += `
|
|
1248
1060
|
else throw new Error(\`The value $\{JSON.stringify(${input})} does not match schema definition.\`)
|
|
@@ -1258,7 +1070,7 @@ function buildValue (laterCode, locationPath, input, location) {
|
|
|
1258
1070
|
}
|
|
1259
1071
|
}
|
|
1260
1072
|
|
|
1261
|
-
return
|
|
1073
|
+
return code
|
|
1262
1074
|
}
|
|
1263
1075
|
|
|
1264
1076
|
function extendDateTimeType (schema) {
|
package/package.json
CHANGED
package/serializer.js
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
module.exports = class Serializer {
|
|
4
|
+
constructor (options = {}) {
|
|
5
|
+
switch (options.rounding) {
|
|
6
|
+
case 'floor':
|
|
7
|
+
this.parseInteger = Math.floor
|
|
8
|
+
break
|
|
9
|
+
case 'ceil':
|
|
10
|
+
this.parseInteger = Math.ceil
|
|
11
|
+
break
|
|
12
|
+
case 'round':
|
|
13
|
+
this.parseInteger = Math.round
|
|
14
|
+
break
|
|
15
|
+
default:
|
|
16
|
+
this.parseInteger = Math.trunc
|
|
17
|
+
break
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
asAny (i) {
|
|
22
|
+
return JSON.stringify(i)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
asNull () {
|
|
26
|
+
return 'null'
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
asInteger (i) {
|
|
30
|
+
if (typeof i === 'bigint') {
|
|
31
|
+
return i.toString()
|
|
32
|
+
} else if (Number.isInteger(i)) {
|
|
33
|
+
return '' + i
|
|
34
|
+
} else {
|
|
35
|
+
/* eslint no-undef: "off" */
|
|
36
|
+
const integer = this.parseInteger(i)
|
|
37
|
+
if (Number.isNaN(integer)) {
|
|
38
|
+
throw new Error(`The value "${i}" cannot be converted to an integer.`)
|
|
39
|
+
} else {
|
|
40
|
+
return '' + integer
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
asIntegerNullable (i) {
|
|
46
|
+
return i === null ? 'null' : this.asInteger(i)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
asNumber (i) {
|
|
50
|
+
const num = Number(i)
|
|
51
|
+
if (Number.isNaN(num)) {
|
|
52
|
+
throw new Error(`The value "${i}" cannot be converted to a number.`)
|
|
53
|
+
} else {
|
|
54
|
+
return '' + num
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
asNumberNullable (i) {
|
|
59
|
+
return i === null ? 'null' : this.asNumber(i)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
asBoolean (bool) {
|
|
63
|
+
return bool && 'true' || 'false' // eslint-disable-line
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
asBooleanNullable (bool) {
|
|
67
|
+
return bool === null ? 'null' : this.asBoolean(bool)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
asDatetime (date, skipQuotes) {
|
|
71
|
+
const quotes = skipQuotes === true ? '' : '"'
|
|
72
|
+
if (date instanceof Date) {
|
|
73
|
+
return quotes + date.toISOString() + quotes
|
|
74
|
+
}
|
|
75
|
+
return this.asString(date, skipQuotes)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
asDatetimeNullable (date, skipQuotes) {
|
|
79
|
+
return date === null ? 'null' : this.asDatetime(date, skipQuotes)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
asDate (date, skipQuotes) {
|
|
83
|
+
const quotes = skipQuotes === true ? '' : '"'
|
|
84
|
+
if (date instanceof Date) {
|
|
85
|
+
return quotes + new Date(date.getTime() - (date.getTimezoneOffset() * 60000)).toISOString().slice(0, 10) + quotes
|
|
86
|
+
}
|
|
87
|
+
return this.asString(date, skipQuotes)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
asDateNullable (date, skipQuotes) {
|
|
91
|
+
return date === null ? 'null' : this.asDate(date, skipQuotes)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
asTime (date, skipQuotes) {
|
|
95
|
+
const quotes = skipQuotes === true ? '' : '"'
|
|
96
|
+
if (date instanceof Date) {
|
|
97
|
+
return quotes + new Date(date.getTime() - (date.getTimezoneOffset() * 60000)).toISOString().slice(11, 19) + quotes
|
|
98
|
+
}
|
|
99
|
+
return this.asString(date, skipQuotes)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
asTimeNullable (date, skipQuotes) {
|
|
103
|
+
return date === null ? 'null' : this.asTime(date, skipQuotes)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
asString (str, skipQuotes) {
|
|
107
|
+
const quotes = skipQuotes === true ? '' : '"'
|
|
108
|
+
if (str instanceof Date) {
|
|
109
|
+
return quotes + str.toISOString() + quotes
|
|
110
|
+
} else if (str === null) {
|
|
111
|
+
return quotes + quotes
|
|
112
|
+
} else if (str instanceof RegExp) {
|
|
113
|
+
str = str.source
|
|
114
|
+
} else if (typeof str !== 'string') {
|
|
115
|
+
str = str.toString()
|
|
116
|
+
}
|
|
117
|
+
// If we skipQuotes it means that we are using it as test
|
|
118
|
+
// no need to test the string length for the render
|
|
119
|
+
if (skipQuotes) {
|
|
120
|
+
return str
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (str.length < 42) {
|
|
124
|
+
return this.asStringSmall(str)
|
|
125
|
+
} else {
|
|
126
|
+
return JSON.stringify(str)
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
asStringNullable (str) {
|
|
131
|
+
return str === null ? 'null' : this.asString(str)
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// magically escape strings for json
|
|
135
|
+
// relying on their charCodeAt
|
|
136
|
+
// everything below 32 needs JSON.stringify()
|
|
137
|
+
// every string that contain surrogate needs JSON.stringify()
|
|
138
|
+
// 34 and 92 happens all the time, so we
|
|
139
|
+
// have a fast case for them
|
|
140
|
+
asStringSmall (str) {
|
|
141
|
+
const l = str.length
|
|
142
|
+
let result = ''
|
|
143
|
+
let last = 0
|
|
144
|
+
let found = false
|
|
145
|
+
let surrogateFound = false
|
|
146
|
+
let point = 255
|
|
147
|
+
// eslint-disable-next-line
|
|
148
|
+
for (var i = 0; i < l && point >= 32; i++) {
|
|
149
|
+
point = str.charCodeAt(i)
|
|
150
|
+
if (point >= 0xD800 && point <= 0xDFFF) {
|
|
151
|
+
// The current character is a surrogate.
|
|
152
|
+
surrogateFound = true
|
|
153
|
+
}
|
|
154
|
+
if (point === 34 || point === 92) {
|
|
155
|
+
result += str.slice(last, i) + '\\'
|
|
156
|
+
last = i
|
|
157
|
+
found = true
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (!found) {
|
|
162
|
+
result = str
|
|
163
|
+
} else {
|
|
164
|
+
result += str.slice(last)
|
|
165
|
+
}
|
|
166
|
+
return ((point < 32) || (surrogateFound === true)) ? JSON.stringify(str) : '"' + result + '"'
|
|
167
|
+
}
|
|
168
|
+
}
|
package/test/array.test.js
CHANGED
|
@@ -218,6 +218,35 @@ test('array items is a list of schema and additionalItems is true, just the desc
|
|
|
218
218
|
t.equal(result, '{"foo":["foo","bar",1]}')
|
|
219
219
|
})
|
|
220
220
|
|
|
221
|
+
test('array items is a list of schema and additionalItems is true, just the described item is validated', (t) => {
|
|
222
|
+
t.plan(1)
|
|
223
|
+
|
|
224
|
+
const schema = {
|
|
225
|
+
type: 'object',
|
|
226
|
+
properties: {
|
|
227
|
+
foo: {
|
|
228
|
+
type: 'array',
|
|
229
|
+
items: [
|
|
230
|
+
{
|
|
231
|
+
type: 'string'
|
|
232
|
+
},
|
|
233
|
+
{
|
|
234
|
+
type: 'number'
|
|
235
|
+
}
|
|
236
|
+
],
|
|
237
|
+
additionalItems: true
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const stringify = build(schema)
|
|
243
|
+
const result = stringify({
|
|
244
|
+
foo: ['foo']
|
|
245
|
+
})
|
|
246
|
+
|
|
247
|
+
t.equal(result, '{"foo":["foo"]}')
|
|
248
|
+
})
|
|
249
|
+
|
|
221
250
|
test('array items is a list of schema and additionalItems is false', (t) => {
|
|
222
251
|
t.plan(1)
|
|
223
252
|
|
|
File without changes
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const test = require('tap').test
|
|
4
|
+
const fjs = require('..')
|
|
5
|
+
const fs = require('fs')
|
|
6
|
+
const path = require('path')
|
|
7
|
+
|
|
8
|
+
function build (opts) {
|
|
9
|
+
return fjs({
|
|
10
|
+
title: 'default string',
|
|
11
|
+
type: 'object',
|
|
12
|
+
properties: {
|
|
13
|
+
firstName: {
|
|
14
|
+
type: 'string'
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
required: ['firstName']
|
|
18
|
+
}, opts)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const tmpDir = 'test/fixtures'
|
|
22
|
+
|
|
23
|
+
test('activate standalone mode', async (t) => {
|
|
24
|
+
t.plan(2)
|
|
25
|
+
let code = build({ mode: 'standalone' })
|
|
26
|
+
t.type(code, 'string')
|
|
27
|
+
code = code.replace(/fast-json-stringify/g, '../..')
|
|
28
|
+
|
|
29
|
+
const destionation = path.resolve(tmpDir, 'standalone.js')
|
|
30
|
+
|
|
31
|
+
t.teardown(async () => {
|
|
32
|
+
await fs.promises.rm(destionation, { force: true })
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
await fs.promises.writeFile(destionation, code)
|
|
36
|
+
const standalone = require(destionation)
|
|
37
|
+
t.same(standalone({ firstName: 'Foo', surname: 'bar' }), JSON.stringify({ firstName: 'Foo' }), 'surname evicted')
|
|
38
|
+
})
|