fast-json-stringify 5.3.0 → 5.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/ci.yml +1 -0
- package/.taprc +7 -1
- package/index.js +127 -165
- package/lib/location.js +55 -0
- package/lib/standalone.js +12 -11
- package/package.json +5 -5
- package/test/allof.test.js +45 -0
- package/test/anyof.test.js +20 -0
- package/test/array.test.js +18 -0
- package/test/if-then-else.test.js +50 -0
- package/test/ref.test.js +24 -0
package/.github/workflows/ci.yml
CHANGED
package/.taprc
CHANGED
package/index.js
CHANGED
|
@@ -10,6 +10,7 @@ const validate = require('./lib/schema-validator')
|
|
|
10
10
|
const Serializer = require('./lib/serializer')
|
|
11
11
|
const Validator = require('./lib/validator')
|
|
12
12
|
const RefResolver = require('./lib/ref-resolver')
|
|
13
|
+
const Location = require('./lib/location')
|
|
13
14
|
|
|
14
15
|
let largeArraySize = 2e4
|
|
15
16
|
let largeArrayMechanism = 'default'
|
|
@@ -40,21 +41,13 @@ function isValidSchema (schema, name) {
|
|
|
40
41
|
}
|
|
41
42
|
}
|
|
42
43
|
|
|
43
|
-
function mergeLocation (location, key) {
|
|
44
|
-
return {
|
|
45
|
-
schema: location.schema[key],
|
|
46
|
-
schemaId: location.schemaId,
|
|
47
|
-
jsonPointer: location.jsonPointer + '/' + key
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
44
|
function resolveRef (location, ref) {
|
|
52
45
|
let hashIndex = ref.indexOf('#')
|
|
53
46
|
if (hashIndex === -1) {
|
|
54
47
|
hashIndex = ref.length
|
|
55
48
|
}
|
|
56
49
|
|
|
57
|
-
const schemaId = ref.slice(0, hashIndex) || location.
|
|
50
|
+
const schemaId = ref.slice(0, hashIndex) || location.getOriginSchemaId()
|
|
58
51
|
const jsonPointer = ref.slice(hashIndex) || '#'
|
|
59
52
|
|
|
60
53
|
const schema = refResolver.getSchema(schemaId, jsonPointer)
|
|
@@ -63,39 +56,42 @@ function resolveRef (location, ref) {
|
|
|
63
56
|
throw new Error(`Cannot find reference "${ref}"`)
|
|
64
57
|
}
|
|
65
58
|
|
|
59
|
+
if (location.isValidated) {
|
|
60
|
+
validatorSchemasIds.add(schemaId)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const newLocation = new Location(schema, schemaId, jsonPointer, location.isValidated)
|
|
66
64
|
if (schema.$ref !== undefined) {
|
|
67
|
-
return resolveRef(
|
|
65
|
+
return resolveRef(newLocation, schema.$ref)
|
|
68
66
|
}
|
|
69
67
|
|
|
70
|
-
return
|
|
68
|
+
return newLocation
|
|
71
69
|
}
|
|
72
70
|
|
|
73
71
|
const contextFunctionsNamesBySchema = new Map()
|
|
74
72
|
|
|
75
73
|
let rootSchemaId = null
|
|
76
74
|
let refResolver = null
|
|
77
|
-
let validator = null
|
|
78
75
|
let contextFunctions = null
|
|
76
|
+
let validatorSchemasIds = null
|
|
79
77
|
|
|
80
78
|
function build (schema, options) {
|
|
81
79
|
contextFunctionsNamesBySchema.clear()
|
|
82
80
|
|
|
83
81
|
contextFunctions = []
|
|
82
|
+
validatorSchemasIds = new Set()
|
|
84
83
|
options = options || {}
|
|
85
84
|
|
|
86
85
|
refResolver = new RefResolver()
|
|
87
|
-
validator = new Validator(options.ajv)
|
|
88
86
|
|
|
89
87
|
rootSchemaId = schema.$id || randomUUID()
|
|
90
88
|
|
|
91
89
|
isValidSchema(schema)
|
|
92
|
-
validator.addSchema(schema, rootSchemaId)
|
|
93
90
|
refResolver.addSchema(schema, rootSchemaId)
|
|
94
91
|
|
|
95
92
|
if (options.schema) {
|
|
96
93
|
for (const key of Object.keys(options.schema)) {
|
|
97
94
|
isValidSchema(options.schema[key], key)
|
|
98
|
-
validator.addSchema(options.schema[key], key)
|
|
99
95
|
refResolver.addSchema(options.schema[key], key)
|
|
100
96
|
}
|
|
101
97
|
}
|
|
@@ -110,7 +106,7 @@ function build (schema, options) {
|
|
|
110
106
|
if (validLargeArrayMechanisms.includes(options.largeArrayMechanism)) {
|
|
111
107
|
largeArrayMechanism = options.largeArrayMechanism
|
|
112
108
|
} else {
|
|
113
|
-
throw new Error(`Unsupported large array mechanism ${options.
|
|
109
|
+
throw new Error(`Unsupported large array mechanism ${options.largeArrayMechanism}`)
|
|
114
110
|
}
|
|
115
111
|
}
|
|
116
112
|
|
|
@@ -122,9 +118,7 @@ function build (schema, options) {
|
|
|
122
118
|
}
|
|
123
119
|
}
|
|
124
120
|
|
|
125
|
-
const
|
|
126
|
-
|
|
127
|
-
const location = { schema, schemaId: rootSchemaId, jsonPointer: '#' }
|
|
121
|
+
const location = new Location(schema, rootSchemaId)
|
|
128
122
|
const code = buildValue(location, 'input')
|
|
129
123
|
|
|
130
124
|
const contextFunctionCode = `
|
|
@@ -135,7 +129,15 @@ function build (schema, options) {
|
|
|
135
129
|
}
|
|
136
130
|
${contextFunctions.join('\n')}
|
|
137
131
|
return main
|
|
138
|
-
|
|
132
|
+
`
|
|
133
|
+
|
|
134
|
+
const serializer = new Serializer(options)
|
|
135
|
+
const validator = new Validator(options.ajv)
|
|
136
|
+
|
|
137
|
+
for (const schemaId of validatorSchemasIds) {
|
|
138
|
+
const schema = refResolver.getSchema(schemaId)
|
|
139
|
+
validator.addSchema(schema, schemaId)
|
|
140
|
+
}
|
|
139
141
|
|
|
140
142
|
const dependenciesName = ['validator', 'serializer', contextFunctionCode]
|
|
141
143
|
|
|
@@ -154,8 +156,9 @@ function build (schema, options) {
|
|
|
154
156
|
|
|
155
157
|
if (options.mode === 'standalone') {
|
|
156
158
|
// lazy load
|
|
159
|
+
const isValidatorUsed = validatorSchemasIds.size > 0
|
|
157
160
|
const buildStandaloneCode = require('./lib/standalone')
|
|
158
|
-
return buildStandaloneCode(options, validator, contextFunctionCode)
|
|
161
|
+
return buildStandaloneCode(options, validator, isValidatorUsed, contextFunctionCode)
|
|
159
162
|
}
|
|
160
163
|
|
|
161
164
|
/* eslint no-new-func: "off" */
|
|
@@ -163,9 +166,9 @@ function build (schema, options) {
|
|
|
163
166
|
const stringifyFunc = contextFunc(validator, serializer)
|
|
164
167
|
|
|
165
168
|
refResolver = null
|
|
166
|
-
validator = null
|
|
167
169
|
rootSchemaId = null
|
|
168
170
|
contextFunctions = null
|
|
171
|
+
validatorSchemasIds = null
|
|
169
172
|
contextFunctionsNamesBySchema.clear()
|
|
170
173
|
|
|
171
174
|
return stringifyFunc
|
|
@@ -228,106 +231,82 @@ function inferTypeByKeyword (schema) {
|
|
|
228
231
|
return schema.type
|
|
229
232
|
}
|
|
230
233
|
|
|
231
|
-
function
|
|
234
|
+
function buildExtraObjectPropertiesSerializer (location) {
|
|
232
235
|
const schema = location.schema
|
|
233
|
-
const
|
|
236
|
+
const propertiesKeys = Object.keys(schema.properties || {})
|
|
237
|
+
|
|
234
238
|
let code = `
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
+
const propertiesKeys = ${JSON.stringify(propertiesKeys)}
|
|
240
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
241
|
+
if (
|
|
242
|
+
propertiesKeys.includes(key) ||
|
|
243
|
+
value === undefined ||
|
|
244
|
+
typeof value === 'function' ||
|
|
245
|
+
typeof value === 'symbol'
|
|
246
|
+
) continue
|
|
239
247
|
`
|
|
240
248
|
|
|
241
|
-
const patternPropertiesLocation =
|
|
242
|
-
|
|
243
|
-
let ppLocation = mergeLocation(patternPropertiesLocation, regex)
|
|
244
|
-
if (pp[regex].$ref) {
|
|
245
|
-
ppLocation = resolveRef(ppLocation, pp[regex].$ref)
|
|
246
|
-
pp[regex] = ppLocation.schema
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
try {
|
|
250
|
-
RegExp(regex)
|
|
251
|
-
} catch (err) {
|
|
252
|
-
throw new Error(`${err.message}. Found at ${regex} matching ${JSON.stringify(pp[regex])}`)
|
|
253
|
-
}
|
|
249
|
+
const patternPropertiesLocation = location.getPropertyLocation('patternProperties')
|
|
250
|
+
const patternPropertiesSchema = patternPropertiesLocation.schema
|
|
254
251
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
${addComma}
|
|
259
|
-
json += serializer.asString(keys[i]) + ':'
|
|
260
|
-
${valueCode}
|
|
261
|
-
continue
|
|
262
|
-
}
|
|
263
|
-
`
|
|
264
|
-
})
|
|
265
|
-
if (schema.additionalProperties) {
|
|
266
|
-
code += additionalProperty(location)
|
|
267
|
-
}
|
|
252
|
+
if (patternPropertiesSchema !== undefined) {
|
|
253
|
+
for (const propertyKey in patternPropertiesSchema) {
|
|
254
|
+
const propertyLocation = patternPropertiesLocation.getPropertyLocation(propertyKey)
|
|
268
255
|
|
|
269
|
-
|
|
256
|
+
try {
|
|
257
|
+
RegExp(propertyKey)
|
|
258
|
+
} catch (err) {
|
|
259
|
+
const jsonPointer = propertyLocation.getSchemaRef()
|
|
260
|
+
throw new Error(`${err.message}. Invalid pattern property regexp key ${propertyKey} at ${jsonPointer}`)
|
|
270
261
|
}
|
|
271
|
-
`
|
|
272
|
-
return code
|
|
273
|
-
}
|
|
274
262
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
let code = ''
|
|
278
|
-
if (ap === true) {
|
|
279
|
-
code += `
|
|
280
|
-
if (obj[keys[i]] !== undefined && typeof obj[keys[i]] !== 'function' && typeof obj[keys[i]] !== 'symbol') {
|
|
263
|
+
code += `
|
|
264
|
+
if (/${propertyKey.replace(/\\*\//g, '\\/')}/.test(key)) {
|
|
281
265
|
${addComma}
|
|
282
|
-
json += serializer.asString(
|
|
266
|
+
json += serializer.asString(key) + ':'
|
|
267
|
+
${buildValue(propertyLocation, 'value')}
|
|
268
|
+
continue
|
|
283
269
|
}
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
return code
|
|
270
|
+
`
|
|
271
|
+
}
|
|
287
272
|
}
|
|
288
273
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
apLocation = resolveRef(apLocation, apLocation.schema.$ref)
|
|
292
|
-
}
|
|
274
|
+
const additionalPropertiesLocation = location.getPropertyLocation('additionalProperties')
|
|
275
|
+
const additionalPropertiesSchema = additionalPropertiesLocation.schema
|
|
293
276
|
|
|
294
|
-
|
|
277
|
+
if (additionalPropertiesSchema !== undefined) {
|
|
278
|
+
if (additionalPropertiesSchema === true) {
|
|
279
|
+
code += `
|
|
280
|
+
${addComma}
|
|
281
|
+
json += serializer.asString(key) + ':' + JSON.stringify(value)
|
|
282
|
+
`
|
|
283
|
+
} else {
|
|
284
|
+
const propertyLocation = location.getPropertyLocation('additionalProperties')
|
|
285
|
+
code += `
|
|
286
|
+
${addComma}
|
|
287
|
+
json += serializer.asString(key) + ':'
|
|
288
|
+
${buildValue(propertyLocation, 'value')}
|
|
289
|
+
`
|
|
290
|
+
}
|
|
291
|
+
}
|
|
295
292
|
|
|
296
293
|
code += `
|
|
297
|
-
|
|
298
|
-
json += serializer.asString(keys[i]) + ':'
|
|
299
|
-
${valueCode}
|
|
294
|
+
}
|
|
300
295
|
`
|
|
301
|
-
|
|
302
296
|
return code
|
|
303
297
|
}
|
|
304
298
|
|
|
305
|
-
function
|
|
306
|
-
return `
|
|
307
|
-
var properties = ${JSON.stringify(location.schema.properties)} || {}
|
|
308
|
-
var keys = Object.keys(obj)
|
|
309
|
-
for (var i = 0; i < keys.length; i++) {
|
|
310
|
-
if (properties[keys[i]]) continue
|
|
311
|
-
${additionalProperty(location)}
|
|
312
|
-
}
|
|
313
|
-
`
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
function buildCode (location) {
|
|
317
|
-
if (location.schema.$ref) {
|
|
318
|
-
location = resolveRef(location, location.schema.$ref)
|
|
319
|
-
}
|
|
320
|
-
|
|
299
|
+
function buildInnerObject (location) {
|
|
321
300
|
const schema = location.schema
|
|
322
301
|
const required = schema.required || []
|
|
323
302
|
|
|
324
303
|
let code = ''
|
|
325
304
|
|
|
326
|
-
const propertiesLocation =
|
|
305
|
+
const propertiesLocation = location.getPropertyLocation('properties')
|
|
327
306
|
Object.keys(schema.properties || {}).forEach((key) => {
|
|
328
|
-
let propertyLocation =
|
|
329
|
-
if (propertyLocation.$ref) {
|
|
330
|
-
propertyLocation = resolveRef(location, propertyLocation.$ref)
|
|
307
|
+
let propertyLocation = propertiesLocation.getPropertyLocation(key)
|
|
308
|
+
if (propertyLocation.schema.$ref) {
|
|
309
|
+
propertyLocation = resolveRef(location, propertyLocation.schema.$ref)
|
|
331
310
|
}
|
|
332
311
|
|
|
333
312
|
const sanitized = JSON.stringify(key)
|
|
@@ -368,17 +347,21 @@ function buildCode (location) {
|
|
|
368
347
|
code += `if (obj['${requiredProperty}'] === undefined) throw new Error('"${requiredProperty}" is required!')\n`
|
|
369
348
|
}
|
|
370
349
|
|
|
350
|
+
if (schema.patternProperties || schema.additionalProperties) {
|
|
351
|
+
code += buildExtraObjectPropertiesSerializer(location)
|
|
352
|
+
}
|
|
353
|
+
|
|
371
354
|
return code
|
|
372
355
|
}
|
|
373
356
|
|
|
374
357
|
function mergeAllOfSchema (location, schema, mergedSchema) {
|
|
375
|
-
const allOfLocation =
|
|
358
|
+
const allOfLocation = location.getPropertyLocation('allOf')
|
|
376
359
|
|
|
377
360
|
for (let i = 0; i < schema.allOf.length; i++) {
|
|
378
361
|
let allOfSchema = schema.allOf[i]
|
|
379
362
|
|
|
380
363
|
if (allOfSchema.$ref) {
|
|
381
|
-
const allOfSchemaLocation =
|
|
364
|
+
const allOfSchemaLocation = allOfLocation.getPropertyLocation(i)
|
|
382
365
|
allOfSchema = resolveRef(allOfSchemaLocation, allOfSchema.$ref).schema
|
|
383
366
|
}
|
|
384
367
|
|
|
@@ -466,24 +449,14 @@ function mergeAllOfSchema (location, schema, mergedSchema) {
|
|
|
466
449
|
delete mergedSchema.allOf
|
|
467
450
|
|
|
468
451
|
mergedSchema.$id = `merged_${randomUUID()}`
|
|
469
|
-
validator.addSchema(mergedSchema)
|
|
470
452
|
refResolver.addSchema(mergedSchema)
|
|
471
|
-
location.
|
|
472
|
-
location.jsonPointer = '#'
|
|
453
|
+
location.addMergedSchema(mergedSchema, mergedSchema.$id)
|
|
473
454
|
}
|
|
474
455
|
|
|
475
|
-
function
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
if (schema.patternProperties) {
|
|
479
|
-
code += addPatternProperties(location)
|
|
480
|
-
} else if (schema.additionalProperties && !schema.patternProperties) {
|
|
481
|
-
code += addAdditionalProperties(location)
|
|
482
|
-
}
|
|
483
|
-
return code
|
|
484
|
-
}
|
|
456
|
+
function addIfThenElse (location, input) {
|
|
457
|
+
location.isValidated = true
|
|
458
|
+
validatorSchemasIds.add(location.getSchemaId())
|
|
485
459
|
|
|
486
|
-
function addIfThenElse (location) {
|
|
487
460
|
const schema = merge({}, location.schema)
|
|
488
461
|
const thenSchema = schema.then
|
|
489
462
|
const elseSchema = schema.else || { additionalProperties: true }
|
|
@@ -492,41 +465,22 @@ function addIfThenElse (location) {
|
|
|
492
465
|
delete schema.then
|
|
493
466
|
delete schema.else
|
|
494
467
|
|
|
495
|
-
const ifLocation =
|
|
496
|
-
const ifSchemaRef = ifLocation.
|
|
468
|
+
const ifLocation = location.getPropertyLocation('if')
|
|
469
|
+
const ifSchemaRef = ifLocation.getSchemaRef()
|
|
497
470
|
|
|
498
|
-
|
|
499
|
-
if (validator.validate("${ifSchemaRef}", obj)) {
|
|
500
|
-
`
|
|
501
|
-
|
|
502
|
-
const thenLocation = mergeLocation(location, 'then')
|
|
471
|
+
const thenLocation = location.getPropertyLocation('then')
|
|
503
472
|
thenLocation.schema = merge(schema, thenSchema)
|
|
504
473
|
|
|
505
|
-
|
|
506
|
-
code += addIfThenElse(thenLocation)
|
|
507
|
-
} else {
|
|
508
|
-
code += buildInnerObject(thenLocation)
|
|
509
|
-
}
|
|
510
|
-
code += `
|
|
511
|
-
}
|
|
512
|
-
`
|
|
513
|
-
|
|
514
|
-
const elseLocation = mergeLocation(location, 'else')
|
|
474
|
+
const elseLocation = location.getPropertyLocation('else')
|
|
515
475
|
elseLocation.schema = merge(schema, elseSchema)
|
|
516
476
|
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
code += buildInnerObject(elseLocation)
|
|
525
|
-
}
|
|
526
|
-
code += `
|
|
527
|
-
}
|
|
528
|
-
`
|
|
529
|
-
return code
|
|
477
|
+
return `
|
|
478
|
+
if (validator.validate("${ifSchemaRef}", ${input})) {
|
|
479
|
+
${buildValue(thenLocation, input)}
|
|
480
|
+
} else {
|
|
481
|
+
${buildValue(elseLocation, input)}
|
|
482
|
+
}
|
|
483
|
+
`
|
|
530
484
|
}
|
|
531
485
|
|
|
532
486
|
function toJSON (variableName) {
|
|
@@ -546,10 +500,14 @@ function buildObject (location) {
|
|
|
546
500
|
const functionName = generateFuncName()
|
|
547
501
|
contextFunctionsNamesBySchema.set(schema, functionName)
|
|
548
502
|
|
|
549
|
-
|
|
503
|
+
let schemaRef = location.getSchemaRef()
|
|
504
|
+
if (schemaRef.startsWith(rootSchemaId)) {
|
|
505
|
+
schemaRef = schemaRef.replace(rootSchemaId, '')
|
|
506
|
+
}
|
|
507
|
+
|
|
550
508
|
let functionCode = `
|
|
551
509
|
function ${functionName} (input) {
|
|
552
|
-
// ${
|
|
510
|
+
// ${schemaRef}
|
|
553
511
|
`
|
|
554
512
|
|
|
555
513
|
functionCode += `
|
|
@@ -558,12 +516,7 @@ function buildObject (location) {
|
|
|
558
516
|
var addComma = false
|
|
559
517
|
`
|
|
560
518
|
|
|
561
|
-
|
|
562
|
-
functionCode += addIfThenElse(location)
|
|
563
|
-
} else {
|
|
564
|
-
functionCode += buildInnerObject(location)
|
|
565
|
-
}
|
|
566
|
-
|
|
519
|
+
functionCode += buildInnerObject(location)
|
|
567
520
|
functionCode += `
|
|
568
521
|
json += '}'
|
|
569
522
|
return json
|
|
@@ -577,7 +530,7 @@ function buildObject (location) {
|
|
|
577
530
|
function buildArray (location) {
|
|
578
531
|
const schema = location.schema
|
|
579
532
|
|
|
580
|
-
let itemsLocation =
|
|
533
|
+
let itemsLocation = location.getPropertyLocation('items')
|
|
581
534
|
itemsLocation.schema = itemsLocation.schema || {}
|
|
582
535
|
|
|
583
536
|
if (itemsLocation.schema.$ref) {
|
|
@@ -593,10 +546,14 @@ function buildArray (location) {
|
|
|
593
546
|
const functionName = generateFuncName()
|
|
594
547
|
contextFunctionsNamesBySchema.set(schema, functionName)
|
|
595
548
|
|
|
596
|
-
|
|
549
|
+
let schemaRef = location.getSchemaRef()
|
|
550
|
+
if (schemaRef.startsWith(rootSchemaId)) {
|
|
551
|
+
schemaRef = schemaRef.replace(rootSchemaId, '')
|
|
552
|
+
}
|
|
553
|
+
|
|
597
554
|
let functionCode = `
|
|
598
555
|
function ${functionName} (obj) {
|
|
599
|
-
// ${
|
|
556
|
+
// ${schemaRef}
|
|
600
557
|
`
|
|
601
558
|
|
|
602
559
|
functionCode += `
|
|
@@ -629,7 +586,7 @@ function buildArray (location) {
|
|
|
629
586
|
if (Array.isArray(itemsSchema)) {
|
|
630
587
|
for (let i = 0; i < itemsSchema.length; i++) {
|
|
631
588
|
const item = itemsSchema[i]
|
|
632
|
-
const tmpRes = buildValue(
|
|
589
|
+
const tmpRes = buildValue(itemsLocation.getPropertyLocation(i), `obj[${i}]`)
|
|
633
590
|
functionCode += `
|
|
634
591
|
if (${i} < arrayLength) {
|
|
635
592
|
if (${buildArrayTypeCondition(item.type, `[${i}]`)}) {
|
|
@@ -725,11 +682,11 @@ function buildMultiTypeSerializer (location, input) {
|
|
|
725
682
|
|
|
726
683
|
let code = ''
|
|
727
684
|
|
|
728
|
-
const locationClone = clone(location)
|
|
729
685
|
types.forEach((type, index) => {
|
|
686
|
+
location.schema = { ...location.schema, type }
|
|
687
|
+
const nestedResult = buildSingleTypeSerializer(location, input)
|
|
688
|
+
|
|
730
689
|
const statement = index === 0 ? 'if' : 'else if'
|
|
731
|
-
locationClone.schema.type = type
|
|
732
|
-
const nestedResult = buildSingleTypeSerializer(locationClone, input)
|
|
733
690
|
switch (type) {
|
|
734
691
|
case 'null':
|
|
735
692
|
code += `
|
|
@@ -869,11 +826,13 @@ function buildValue (location, input) {
|
|
|
869
826
|
}
|
|
870
827
|
}
|
|
871
828
|
|
|
829
|
+
if (schema.if && schema.then) {
|
|
830
|
+
return addIfThenElse(location, input)
|
|
831
|
+
}
|
|
832
|
+
|
|
872
833
|
if (schema.allOf) {
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
schema = mergedSchema
|
|
876
|
-
location.schema = mergedSchema
|
|
834
|
+
mergeAllOfSchema(location, schema, clone(schema))
|
|
835
|
+
schema = location.schema
|
|
877
836
|
}
|
|
878
837
|
|
|
879
838
|
const type = schema.type
|
|
@@ -881,12 +840,15 @@ function buildValue (location, input) {
|
|
|
881
840
|
let code = ''
|
|
882
841
|
|
|
883
842
|
if (type === undefined && (schema.anyOf || schema.oneOf)) {
|
|
843
|
+
location.isValidated = true
|
|
844
|
+
validatorSchemasIds.add(location.getSchemaId())
|
|
845
|
+
|
|
884
846
|
const type = schema.anyOf ? 'anyOf' : 'oneOf'
|
|
885
|
-
const anyOfLocation =
|
|
847
|
+
const anyOfLocation = location.getPropertyLocation(type)
|
|
886
848
|
|
|
887
849
|
for (let index = 0; index < location.schema[type].length; index++) {
|
|
888
|
-
const optionLocation =
|
|
889
|
-
const schemaRef = optionLocation.
|
|
850
|
+
const optionLocation = anyOfLocation.getPropertyLocation(index)
|
|
851
|
+
const schemaRef = optionLocation.getSchemaRef()
|
|
890
852
|
const nestedResult = buildValue(optionLocation, input)
|
|
891
853
|
code += `
|
|
892
854
|
${index === 0 ? 'if' : 'else if'}(validator.validate("${schemaRef}", ${input}))
|
package/lib/location.js
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
class Location {
|
|
4
|
+
constructor (schema, schemaId, jsonPointer = '#', isValidated = false) {
|
|
5
|
+
this.schema = schema
|
|
6
|
+
this.schemaId = schemaId
|
|
7
|
+
this.jsonPointer = jsonPointer
|
|
8
|
+
this.isValidated = isValidated
|
|
9
|
+
this.mergedSchemaId = null
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
getPropertyLocation (propertyName) {
|
|
13
|
+
const propertyLocation = new Location(
|
|
14
|
+
this.schema[propertyName],
|
|
15
|
+
this.schemaId,
|
|
16
|
+
this.jsonPointer + '/' + propertyName,
|
|
17
|
+
this.isValidated
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
if (this.mergedSchemaId !== null) {
|
|
21
|
+
propertyLocation.addMergedSchema(
|
|
22
|
+
this.schema[propertyName],
|
|
23
|
+
this.mergedSchemaId,
|
|
24
|
+
this.jsonPointer + '/' + propertyName
|
|
25
|
+
)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return propertyLocation
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Use this method to get current schema location.
|
|
32
|
+
// Use it when you need to create reference to the current location.
|
|
33
|
+
getSchemaId () {
|
|
34
|
+
return this.mergedSchemaId || this.schemaId
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Use this method to get original schema id for resolving user schema $refs
|
|
38
|
+
// Don't join it with a JSON pointer to get the current location.
|
|
39
|
+
getOriginSchemaId () {
|
|
40
|
+
return this.schemaId
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
getSchemaRef () {
|
|
44
|
+
const schemaId = this.getSchemaId()
|
|
45
|
+
return schemaId + this.jsonPointer
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
addMergedSchema (mergedSchema, schemaId, jsonPointer = '#') {
|
|
49
|
+
this.schema = mergedSchema
|
|
50
|
+
this.mergedSchemaId = schemaId
|
|
51
|
+
this.jsonPointer = jsonPointer
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
module.exports = Location
|
package/lib/standalone.js
CHANGED
|
@@ -1,20 +1,21 @@
|
|
|
1
1
|
const fs = require('fs')
|
|
2
2
|
const path = require('path')
|
|
3
3
|
|
|
4
|
-
function buildStandaloneCode (options, validator, contextFunctionCode) {
|
|
4
|
+
function buildStandaloneCode (options, validator, isValidatorUsed, contextFunctionCode) {
|
|
5
5
|
const serializerCode = fs.readFileSync(path.join(__dirname, 'serializer.js')).toString()
|
|
6
6
|
let buildAjvCode = ''
|
|
7
|
-
let defaultAjvSchema = ''
|
|
8
|
-
const defaultMeta = validator.ajv.defaultMeta()
|
|
9
|
-
if (typeof defaultMeta === 'string') {
|
|
10
|
-
defaultAjvSchema = defaultMeta
|
|
11
|
-
} else {
|
|
12
|
-
defaultAjvSchema = defaultMeta.$id || defaultMeta.id
|
|
13
|
-
}
|
|
14
|
-
const shouldUseAjv = contextFunctionCode.indexOf('validator') !== -1
|
|
15
|
-
// we need to export the custom json schema
|
|
16
7
|
let ajvSchemasCode = ''
|
|
17
|
-
|
|
8
|
+
|
|
9
|
+
if (isValidatorUsed) {
|
|
10
|
+
let defaultAjvSchema = ''
|
|
11
|
+
// we need to export the custom json schema
|
|
12
|
+
const defaultMeta = validator.ajv.defaultMeta()
|
|
13
|
+
if (typeof defaultMeta === 'string') {
|
|
14
|
+
defaultAjvSchema = defaultMeta
|
|
15
|
+
} else {
|
|
16
|
+
defaultAjvSchema = defaultMeta.$id || defaultMeta.id
|
|
17
|
+
}
|
|
18
|
+
|
|
18
19
|
ajvSchemasCode += `const validator = new Validator(${JSON.stringify(options.ajv || {})})\n`
|
|
19
20
|
for (const [id, schema] of Object.entries(validator.ajv.schemas)) {
|
|
20
21
|
// should skip ajv default schema
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fast-json-stringify",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.4.0",
|
|
4
4
|
"description": "Stringify your JSON at max speed",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"types": "index.d.ts",
|
|
@@ -9,13 +9,13 @@
|
|
|
9
9
|
"bench:cmp": "node ./benchmark/bench-cmp-branch.js",
|
|
10
10
|
"bench:cmp:ci": "node ./benchmark/bench-cmp-branch.js --ci",
|
|
11
11
|
"benchmark": "node ./benchmark/bench-cmp-lib.js",
|
|
12
|
+
"lint": "standard",
|
|
12
13
|
"lint:fix": "standard --fix",
|
|
13
|
-
"test:lint": "standard",
|
|
14
14
|
"test:typescript": "tsc --project ./test/types/tsconfig.json && tsd",
|
|
15
15
|
"test:unit": "tap -J test/*.test.js test/**/*.test.js",
|
|
16
|
-
"test": "npm run test:
|
|
16
|
+
"test": "npm run test:unit && npm run test:typescript"
|
|
17
17
|
},
|
|
18
|
-
"precommit": "test",
|
|
18
|
+
"precommit": ["lint", "test"],
|
|
19
19
|
"repository": {
|
|
20
20
|
"type": "git",
|
|
21
21
|
"url": "git+https://github.com/fastify/fast-json-stringify.git"
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
"simple-git": "^3.7.1",
|
|
46
46
|
"standard": "^17.0.0",
|
|
47
47
|
"tap": "^16.0.1",
|
|
48
|
-
"tsd": "^0.
|
|
48
|
+
"tsd": "^0.24.1",
|
|
49
49
|
"typescript": "^4.0.2",
|
|
50
50
|
"webpack": "^5.40.0"
|
|
51
51
|
},
|
package/test/allof.test.js
CHANGED
|
@@ -437,3 +437,48 @@ test('object with external $refs in allOf', (t) => {
|
|
|
437
437
|
})
|
|
438
438
|
t.equal(value, '{"id1":1,"id2":2}')
|
|
439
439
|
})
|
|
440
|
+
|
|
441
|
+
test('allof with local anchor reference', (t) => {
|
|
442
|
+
t.plan(1)
|
|
443
|
+
|
|
444
|
+
const externalSchemas = {
|
|
445
|
+
Test: {
|
|
446
|
+
$id: 'Test',
|
|
447
|
+
definitions: {
|
|
448
|
+
Problem: {
|
|
449
|
+
type: 'object',
|
|
450
|
+
properties: {
|
|
451
|
+
type: {
|
|
452
|
+
type: 'string'
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
},
|
|
456
|
+
ValidationFragment: {
|
|
457
|
+
type: 'string'
|
|
458
|
+
},
|
|
459
|
+
ValidationErrorProblem: {
|
|
460
|
+
type: 'object',
|
|
461
|
+
allOf: [
|
|
462
|
+
{
|
|
463
|
+
$ref: '#/definitions/Problem'
|
|
464
|
+
},
|
|
465
|
+
{
|
|
466
|
+
type: 'object',
|
|
467
|
+
properties: {
|
|
468
|
+
validation: {
|
|
469
|
+
$ref: '#/definitions/ValidationFragment'
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
]
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
const schema = { $ref: 'Test#/definitions/ValidationErrorProblem' }
|
|
480
|
+
const stringify = build(schema, { schema: externalSchemas })
|
|
481
|
+
const data = { type: 'foo', validation: 'bar' }
|
|
482
|
+
|
|
483
|
+
t.equal(stringify(data), JSON.stringify(data))
|
|
484
|
+
})
|
package/test/anyof.test.js
CHANGED
|
@@ -596,3 +596,23 @@ test('anyOf object with invalid field date of type string with format or null',
|
|
|
596
596
|
const withOneOfStringify = build(withOneOfSchema)
|
|
597
597
|
t.throws(() => withOneOfStringify({ prop: toStringify }))
|
|
598
598
|
})
|
|
599
|
+
|
|
600
|
+
test('anyOf with a nested external schema', (t) => {
|
|
601
|
+
t.plan(1)
|
|
602
|
+
|
|
603
|
+
const externalSchemas = {
|
|
604
|
+
schema1: {
|
|
605
|
+
definitions: {
|
|
606
|
+
def1: {
|
|
607
|
+
$id: 'external',
|
|
608
|
+
type: 'string'
|
|
609
|
+
}
|
|
610
|
+
},
|
|
611
|
+
type: 'number'
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
const schema = { anyOf: [{ $ref: 'external' }] }
|
|
615
|
+
|
|
616
|
+
const stringify = build(schema, { schema: externalSchemas })
|
|
617
|
+
t.equal(stringify('foo'), '"foo"')
|
|
618
|
+
})
|
package/test/array.test.js
CHANGED
|
@@ -4,6 +4,24 @@ const test = require('tap').test
|
|
|
4
4
|
const validator = require('is-my-json-valid')
|
|
5
5
|
const build = require('..')
|
|
6
6
|
|
|
7
|
+
test('error on invalid largeArrayMechanism', (t) => {
|
|
8
|
+
t.plan(1)
|
|
9
|
+
|
|
10
|
+
t.throws(() => build({
|
|
11
|
+
title: 'large array of null values with default mechanism',
|
|
12
|
+
type: 'object',
|
|
13
|
+
properties: {
|
|
14
|
+
ids: {
|
|
15
|
+
type: 'array',
|
|
16
|
+
items: { type: 'null' }
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}, {
|
|
20
|
+
largeArraySize: 2e4,
|
|
21
|
+
largeArrayMechanism: 'invalid'
|
|
22
|
+
}), Error('Unsupported large array mechanism invalid'))
|
|
23
|
+
})
|
|
24
|
+
|
|
7
25
|
function buildTest (schema, toStringify, options) {
|
|
8
26
|
test(`render a ${schema.title} as JSON`, (t) => {
|
|
9
27
|
t.plan(3)
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const t = require('tap')
|
|
4
|
+
const { DateTime } = require('luxon')
|
|
4
5
|
const build = require('..')
|
|
5
6
|
|
|
6
7
|
const schema = {
|
|
@@ -368,3 +369,52 @@ t.test('nested if/then', t => {
|
|
|
368
369
|
JSON.stringify({ a: 'A', bar: 'bar', bar1: 'bar1' })
|
|
369
370
|
)
|
|
370
371
|
})
|
|
372
|
+
|
|
373
|
+
t.test('if/else with string format', (t) => {
|
|
374
|
+
t.plan(2)
|
|
375
|
+
|
|
376
|
+
const schema = {
|
|
377
|
+
if: { type: 'string' },
|
|
378
|
+
then: { type: 'string', format: 'date' },
|
|
379
|
+
else: { const: 'Invalid' }
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
const stringify = build(schema)
|
|
383
|
+
|
|
384
|
+
const date = new Date()
|
|
385
|
+
|
|
386
|
+
t.equal(stringify(date), `"${DateTime.fromJSDate(date).toISODate()}"`)
|
|
387
|
+
t.equal(stringify('Invalid'), '"Invalid"')
|
|
388
|
+
})
|
|
389
|
+
|
|
390
|
+
t.test('if/else with const integers', (t) => {
|
|
391
|
+
t.plan(2)
|
|
392
|
+
|
|
393
|
+
const schema = {
|
|
394
|
+
type: 'number',
|
|
395
|
+
if: { type: 'number', minimum: 42 },
|
|
396
|
+
then: { const: 66 },
|
|
397
|
+
else: { const: 33 }
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
const stringify = build(schema)
|
|
401
|
+
|
|
402
|
+
t.equal(stringify(100.32), '66')
|
|
403
|
+
t.equal(stringify(10.12), '33')
|
|
404
|
+
})
|
|
405
|
+
|
|
406
|
+
t.test('if/else with array', (t) => {
|
|
407
|
+
t.plan(2)
|
|
408
|
+
|
|
409
|
+
const schema = {
|
|
410
|
+
type: 'array',
|
|
411
|
+
if: { type: 'array', maxItems: 1 },
|
|
412
|
+
then: { items: { type: 'string' } },
|
|
413
|
+
else: { items: { type: 'number' } }
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
const stringify = build(schema)
|
|
417
|
+
|
|
418
|
+
t.equal(stringify(['1']), JSON.stringify(['1']))
|
|
419
|
+
t.equal(stringify(['1', '2']), JSON.stringify([1, 2]))
|
|
420
|
+
})
|
package/test/ref.test.js
CHANGED
|
@@ -1981,3 +1981,27 @@ test('nested schema should overwrite anchor scope', (t) => {
|
|
|
1981
1981
|
t.equal(output, JSON.stringify(data))
|
|
1982
1982
|
t.throws(() => build({ $ref: 'root#anchor' }, { schema: externalSchema }))
|
|
1983
1983
|
})
|
|
1984
|
+
|
|
1985
|
+
test('object property reference with default value', (t) => {
|
|
1986
|
+
t.plan(1)
|
|
1987
|
+
|
|
1988
|
+
const schema = {
|
|
1989
|
+
definitions: {
|
|
1990
|
+
prop: {
|
|
1991
|
+
type: 'string',
|
|
1992
|
+
default: 'foo'
|
|
1993
|
+
}
|
|
1994
|
+
},
|
|
1995
|
+
type: 'object',
|
|
1996
|
+
properties: {
|
|
1997
|
+
prop: {
|
|
1998
|
+
$ref: '#/definitions/prop'
|
|
1999
|
+
}
|
|
2000
|
+
}
|
|
2001
|
+
}
|
|
2002
|
+
|
|
2003
|
+
const stringify = build(schema)
|
|
2004
|
+
const output = stringify({})
|
|
2005
|
+
|
|
2006
|
+
t.equal(output, '{"prop":"foo"}')
|
|
2007
|
+
})
|