fast-json-stringify 6.3.0 → 6.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 -1
- package/.github/workflows/lock-threads.yml +19 -0
- package/LICENSE +1 -3
- package/benchmark/bench-cmp-lib.js +60 -44
- package/index.js +2 -2
- package/package.json +7 -7
- package/test/anyof.test.js +20 -0
- package/test/array.test.js +22 -0
- package/test/debug-mode.test.js +21 -0
- package/test/if-then-else.test.js +72 -0
- package/test/invalidSchema.test.js +20 -0
- package/test/issue-836.test.js +183 -0
- package/test/json-add-comma.test.js +57 -57
- package/test/oneof.test.js +20 -0
- package/test/ref.test.js +31 -0
- package/test/typebox.test.js +1 -1
- package/types/{index.test-d.ts → index.tst.ts} +17 -17
- package/.github/stale.yml +0 -21
package/.github/workflows/ci.yml
CHANGED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
name: Lock Threads
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
schedule:
|
|
5
|
+
- cron: '0 0 1 * *'
|
|
6
|
+
workflow_dispatch:
|
|
7
|
+
|
|
8
|
+
concurrency:
|
|
9
|
+
group: lock
|
|
10
|
+
|
|
11
|
+
permissions:
|
|
12
|
+
contents: read
|
|
13
|
+
|
|
14
|
+
jobs:
|
|
15
|
+
lock-threads:
|
|
16
|
+
permissions:
|
|
17
|
+
issues: write
|
|
18
|
+
pull-requests: write
|
|
19
|
+
uses: fastify/workflows/.github/workflows/lock-threads.yml@v6
|
package/LICENSE
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
MIT License
|
|
2
2
|
|
|
3
3
|
Copyright (c) 2016-present Matteo Collina
|
|
4
|
-
Copyright (c) 2016-present The Fastify team
|
|
5
|
-
|
|
6
|
-
The Fastify team members are listed at https://github.com/fastify/fastify#team.
|
|
4
|
+
Copyright (c) 2016-present The Fastify team <https://github.com/fastify/fastify#team>
|
|
7
5
|
|
|
8
6
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
9
7
|
of this software and associated documentation files (the "Software"), to deal
|
|
@@ -169,155 +169,171 @@ for (let i = STR_LEN; i < LARGE_ARRAY_SIZE; ++i) {
|
|
|
169
169
|
}
|
|
170
170
|
}
|
|
171
171
|
|
|
172
|
-
Number(str)
|
|
173
|
-
|
|
174
172
|
for (let i = 0; i < MULTI_ARRAY_LENGTH; i++) {
|
|
175
173
|
multiArray[i] = obj
|
|
176
174
|
}
|
|
177
175
|
|
|
178
|
-
suite.add('
|
|
176
|
+
suite.add('fast-json-stringify: creation', function () {
|
|
179
177
|
FJS(schema)
|
|
180
178
|
})
|
|
181
|
-
suite.add('
|
|
179
|
+
suite.add('compile-json-stringify: creation', function () {
|
|
182
180
|
CJS(schemaCJS)
|
|
183
181
|
})
|
|
184
|
-
suite.add('AJV
|
|
182
|
+
suite.add('AJV: creation', function () {
|
|
185
183
|
ajv.compileSerializer(schemaAJVJTD)
|
|
186
184
|
})
|
|
187
|
-
suite.add('json-accelerator creation', function () {
|
|
185
|
+
suite.add('json-accelerator: creation', function () {
|
|
188
186
|
createAccelerator(schema)
|
|
189
187
|
})
|
|
190
188
|
|
|
191
|
-
suite.add('JSON.stringify array', function () {
|
|
189
|
+
suite.add('JSON.stringify: array', function () {
|
|
192
190
|
JSON.stringify(multiArray)
|
|
193
191
|
})
|
|
194
192
|
|
|
195
|
-
suite.add('fast-json-stringify array
|
|
193
|
+
suite.add('fast-json-stringify [default]: array', function () {
|
|
196
194
|
stringifyArrayDefault(multiArray)
|
|
197
195
|
})
|
|
198
196
|
|
|
199
|
-
suite.add('json-accelerator array', function () {
|
|
197
|
+
suite.add('json-accelerator: array', function () {
|
|
200
198
|
accelArray(multiArray)
|
|
201
199
|
})
|
|
202
200
|
|
|
203
|
-
suite.add('fast-json-stringify
|
|
201
|
+
suite.add('fast-json-stringify [json-stringify]: array', function () {
|
|
204
202
|
stringifyArrayJSONStringify(multiArray)
|
|
205
203
|
})
|
|
206
204
|
|
|
207
|
-
suite.add('compile-json-stringify array', function () {
|
|
205
|
+
suite.add('compile-json-stringify: array', function () {
|
|
208
206
|
CJSStringifyArray(multiArray)
|
|
209
207
|
})
|
|
210
208
|
|
|
211
|
-
suite.add('AJV
|
|
209
|
+
suite.add('AJV: array', function () {
|
|
212
210
|
ajvSerializeArray(multiArray)
|
|
213
211
|
})
|
|
214
212
|
|
|
215
|
-
suite.add('JSON.stringify large array', function () {
|
|
213
|
+
suite.add('JSON.stringify: large array', function () {
|
|
216
214
|
JSON.stringify(largeArray)
|
|
217
215
|
})
|
|
218
216
|
|
|
219
|
-
suite.add('fast-json-stringify large array
|
|
217
|
+
suite.add('fast-json-stringify [default]: large array', function () {
|
|
220
218
|
stringifyArrayDefault(largeArray)
|
|
221
219
|
})
|
|
222
220
|
|
|
223
|
-
suite.add('fast-json-stringify large array
|
|
221
|
+
suite.add('fast-json-stringify [json-stringify]: large array', function () {
|
|
224
222
|
stringifyArrayJSONStringify(largeArray)
|
|
225
223
|
})
|
|
226
224
|
|
|
227
|
-
suite.add('compile-json-stringify large array', function () {
|
|
225
|
+
suite.add('compile-json-stringify: large array', function () {
|
|
228
226
|
CJSStringifyArray(largeArray)
|
|
229
227
|
})
|
|
230
228
|
|
|
231
|
-
suite.add('AJV
|
|
229
|
+
suite.add('AJV: large array', function () {
|
|
232
230
|
ajvSerializeArray(largeArray)
|
|
233
231
|
})
|
|
234
232
|
|
|
235
|
-
suite.add('JSON.stringify long string', function () {
|
|
233
|
+
suite.add('JSON.stringify: long string', function () {
|
|
236
234
|
JSON.stringify(str)
|
|
237
235
|
})
|
|
238
236
|
|
|
239
|
-
suite.add('fast-json-stringify long string', function () {
|
|
237
|
+
suite.add('fast-json-stringify: long string', function () {
|
|
240
238
|
stringifyString(str)
|
|
241
239
|
})
|
|
242
240
|
|
|
243
|
-
suite.add('json-accelerator long string', function () {
|
|
241
|
+
suite.add('json-accelerator: long string', function () {
|
|
244
242
|
stringifyString(str)
|
|
245
243
|
})
|
|
246
244
|
|
|
247
|
-
suite.add('compile-json-stringify long string', function () {
|
|
245
|
+
suite.add('compile-json-stringify: long string', function () {
|
|
248
246
|
CJSStringifyString(str)
|
|
249
247
|
})
|
|
250
248
|
|
|
251
|
-
suite.add('AJV
|
|
249
|
+
suite.add('AJV: long string', function () {
|
|
252
250
|
ajvSerializeString(str)
|
|
253
251
|
})
|
|
254
252
|
|
|
255
|
-
suite.add('JSON.stringify short string', function () {
|
|
253
|
+
suite.add('JSON.stringify: short string', function () {
|
|
256
254
|
JSON.stringify('hello world')
|
|
257
255
|
})
|
|
258
256
|
|
|
259
|
-
suite.add('fast-json-stringify short string', function () {
|
|
257
|
+
suite.add('fast-json-stringify: short string', function () {
|
|
260
258
|
stringifyString('hello world')
|
|
261
259
|
})
|
|
262
260
|
|
|
263
|
-
suite.add('json-accelerator short string', function () {
|
|
261
|
+
suite.add('json-accelerator: short string', function () {
|
|
264
262
|
accelString('hello world')
|
|
265
263
|
})
|
|
266
264
|
|
|
267
|
-
suite.add('compile-json-stringify short string', function () {
|
|
265
|
+
suite.add('compile-json-stringify: short string', function () {
|
|
268
266
|
CJSStringifyString('hello world')
|
|
269
267
|
})
|
|
270
268
|
|
|
271
|
-
suite.add('AJV
|
|
269
|
+
suite.add('AJV: short string', function () {
|
|
272
270
|
ajvSerializeString('hello world')
|
|
273
271
|
})
|
|
274
272
|
|
|
275
|
-
suite.add('JSON.stringify obj', function () {
|
|
273
|
+
suite.add('JSON.stringify: obj', function () {
|
|
276
274
|
JSON.stringify(obj)
|
|
277
275
|
})
|
|
278
276
|
|
|
279
|
-
suite.add('fast-json-stringify obj', function () {
|
|
277
|
+
suite.add('fast-json-stringify: obj', function () {
|
|
280
278
|
stringify(obj)
|
|
281
279
|
})
|
|
282
280
|
|
|
283
|
-
suite.add('json-accelerator obj', function () {
|
|
281
|
+
suite.add('json-accelerator: obj', function () {
|
|
284
282
|
accelStringify(obj)
|
|
285
283
|
})
|
|
286
284
|
|
|
287
|
-
suite.add('compile-json-stringify obj', function () {
|
|
285
|
+
suite.add('compile-json-stringify: obj', function () {
|
|
288
286
|
CJSStringify(obj)
|
|
289
287
|
})
|
|
290
288
|
|
|
291
|
-
suite.add('AJV
|
|
289
|
+
suite.add('AJV: obj', function () {
|
|
292
290
|
ajvSerialize(obj)
|
|
293
291
|
})
|
|
294
292
|
|
|
295
|
-
suite.add('JSON
|
|
293
|
+
suite.add('JSON.stringify: date', function () {
|
|
296
294
|
JSON.stringify(date)
|
|
297
295
|
})
|
|
298
296
|
|
|
299
|
-
suite.add('fast-json-stringify date
|
|
297
|
+
suite.add('fast-json-stringify: date', function () {
|
|
300
298
|
stringifyDate(date)
|
|
301
299
|
})
|
|
302
300
|
|
|
303
|
-
suite.add('json-accelerate date
|
|
301
|
+
suite.add('json-accelerate: date', function () {
|
|
304
302
|
accelDate(date)
|
|
305
303
|
})
|
|
306
304
|
|
|
307
|
-
suite.add('compile-json-stringify date
|
|
305
|
+
suite.add('compile-json-stringify: date', function () {
|
|
308
306
|
CJSStringifyDate(date)
|
|
309
307
|
})
|
|
310
308
|
|
|
311
309
|
suite.run().then(() => {
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
310
|
+
const results = suite.tasks.map(task => ({
|
|
311
|
+
name: task.name,
|
|
312
|
+
hz: task.result.throughput.mean,
|
|
313
|
+
rme: task.result.latency.rme,
|
|
314
|
+
samples: task.result.latency.df + 1
|
|
315
|
+
}))
|
|
316
|
+
|
|
317
|
+
const scenarios = {}
|
|
318
|
+
for (const result of results) {
|
|
319
|
+
const [library, scenario] = result.name.split(':').map(s => s.trim())
|
|
320
|
+
|
|
321
|
+
if (!scenarios[scenario]) scenarios[scenario] = []
|
|
322
|
+
scenarios[scenario].push({ ...result, library })
|
|
323
|
+
}
|
|
316
324
|
|
|
317
|
-
|
|
318
|
-
|
|
325
|
+
for (const [scenario, tasks] of Object.entries(scenarios)) {
|
|
326
|
+
console.log(`\n--- ${scenario} ---`)
|
|
327
|
+
const sorted = tasks.sort((a, b) => b.hz - a.hz)
|
|
328
|
+
const winner = sorted[0]
|
|
319
329
|
|
|
320
|
-
const
|
|
321
|
-
|
|
330
|
+
for (const task of sorted) {
|
|
331
|
+
const formattedHz = task.hz.toLocaleString('en-US', { maximumFractionDigits: 0 })
|
|
332
|
+
const formattedRme = task.rme.toFixed(2)
|
|
333
|
+
const isWinner = task === winner
|
|
334
|
+
const prefix = isWinner ? '🏆 ' : ' '
|
|
335
|
+
|
|
336
|
+
console.log(`${prefix}${task.library.padEnd(40)} x ${formattedHz.padStart(15)} ops/sec ±${formattedRme}% (${task.samples} runs sampled)`)
|
|
337
|
+
}
|
|
322
338
|
}
|
|
323
339
|
}).catch(err => console.error(`Error: ${err.message}`))
|
package/index.js
CHANGED
|
@@ -568,7 +568,7 @@ function buildObject (context, location, input) {
|
|
|
568
568
|
const jsonPointer = location.jsonPointer || ''
|
|
569
569
|
const fullPath = `${schemaId}#${jsonPointer}`
|
|
570
570
|
|
|
571
|
-
if (context.recursivePaths.has(fullPath) || context.buildingSet.has(schema)) {
|
|
571
|
+
if (context.recursivePaths.has(fullPath) || context.buildingSet.has(schema) || schemaId !== '') {
|
|
572
572
|
const functionName = generateFuncName(context)
|
|
573
573
|
context.functionsNamesBySchema.set(schema, functionName)
|
|
574
574
|
|
|
@@ -627,7 +627,7 @@ function buildArray (context, location, input) {
|
|
|
627
627
|
const jsonPointer = location.jsonPointer || ''
|
|
628
628
|
const fullPath = `${schemaId}#${jsonPointer}`
|
|
629
629
|
|
|
630
|
-
if (context.recursivePaths.has(fullPath) || context.buildingSet.has(schema)) {
|
|
630
|
+
if (context.recursivePaths.has(fullPath) || context.buildingSet.has(schema) || schemaId !== '') {
|
|
631
631
|
const functionName = generateFuncName(context)
|
|
632
632
|
context.functionsNamesBySchema.set(schema, functionName)
|
|
633
633
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fast-json-stringify",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.4.0",
|
|
4
4
|
"description": "Stringify your JSON at max speed",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "commonjs",
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
"benchmark": "node --expose-gc ./benchmark/bench-cmp-lib.js",
|
|
13
13
|
"lint": "eslint",
|
|
14
14
|
"lint:fix": "eslint --fix",
|
|
15
|
-
"test:typescript": "
|
|
15
|
+
"test:typescript": "tstyche",
|
|
16
16
|
"test:unit": "c8 node --test",
|
|
17
17
|
"test": "npm run test:unit && npm run test:typescript"
|
|
18
18
|
},
|
|
@@ -62,19 +62,19 @@
|
|
|
62
62
|
}
|
|
63
63
|
],
|
|
64
64
|
"devDependencies": {
|
|
65
|
-
"
|
|
66
|
-
"c8": "^10.1.3",
|
|
65
|
+
"c8": "^11.0.0",
|
|
67
66
|
"cli-select": "^1.1.2",
|
|
68
67
|
"compile-json-stringify": "^0.1.2",
|
|
69
68
|
"eslint": "^9.39.2",
|
|
70
69
|
"fast-json-stringify": ".",
|
|
71
70
|
"is-my-json-valid": "^2.20.6",
|
|
72
71
|
"json-accelerator": "^0.1.7",
|
|
73
|
-
"neostandard": "^0.
|
|
72
|
+
"neostandard": "^0.13.0",
|
|
74
73
|
"simple-git": "^3.30.0",
|
|
75
74
|
"tinybench": "^6.0.0",
|
|
76
|
-
"
|
|
77
|
-
"
|
|
75
|
+
"tstyche": "^7.1.0",
|
|
76
|
+
"typebox": "^1.0.81",
|
|
77
|
+
"typescript": "~6.0.2",
|
|
78
78
|
"webpack": "^5.104.1"
|
|
79
79
|
},
|
|
80
80
|
"dependencies": {
|
package/test/anyof.test.js
CHANGED
|
@@ -790,3 +790,23 @@ test('should build merged schemas twice', (t) => {
|
|
|
790
790
|
t.assert.equal(stringify({ enums: 'BAR' }), '{"enums":"BAR"}')
|
|
791
791
|
}
|
|
792
792
|
})
|
|
793
|
+
|
|
794
|
+
test('invalid anyOf schema', (t) => {
|
|
795
|
+
t.plan(1)
|
|
796
|
+
|
|
797
|
+
const schema = {
|
|
798
|
+
type: 'object',
|
|
799
|
+
properties: {
|
|
800
|
+
prop: {
|
|
801
|
+
anyOf: 'not array' // invalid, anyOf must be array
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
try {
|
|
807
|
+
build(schema)
|
|
808
|
+
t.assert.fail('Should throw')
|
|
809
|
+
} catch (err) {
|
|
810
|
+
t.assert.ok(err.message.includes('schema is invalid'))
|
|
811
|
+
}
|
|
812
|
+
})
|
package/test/array.test.js
CHANGED
|
@@ -386,6 +386,28 @@ test('array items is a schema and additionalItems is false', (t) => {
|
|
|
386
386
|
t.assert.equal(validate({ foo: ['foo', 'bar'] }), true)
|
|
387
387
|
})
|
|
388
388
|
|
|
389
|
+
test('array items is a list of schema and additionalItems is a schema', (t) => {
|
|
390
|
+
t.plan(1)
|
|
391
|
+
|
|
392
|
+
const schema = {
|
|
393
|
+
type: 'object',
|
|
394
|
+
properties: {
|
|
395
|
+
foo: {
|
|
396
|
+
type: 'array',
|
|
397
|
+
items: [
|
|
398
|
+
{ type: 'string' }
|
|
399
|
+
],
|
|
400
|
+
additionalItems: { type: 'number' }
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
const stringify = build(schema)
|
|
406
|
+
const result = stringify({ foo: ['foo', 42] })
|
|
407
|
+
|
|
408
|
+
t.assert.equal(result, '{"foo":["foo",42]}')
|
|
409
|
+
})
|
|
410
|
+
|
|
389
411
|
// https://github.com/fastify/fast-json-stringify/issues/279
|
|
390
412
|
test('object array with anyOf and symbol', (t) => {
|
|
391
413
|
t.plan(1)
|
package/test/debug-mode.test.js
CHANGED
|
@@ -119,3 +119,24 @@ test('debug should restore the same serializer instance', t => {
|
|
|
119
119
|
const compiled = fjs.restore(debugMode)
|
|
120
120
|
t.assert.equal(compiled(3.95), 4)
|
|
121
121
|
})
|
|
122
|
+
|
|
123
|
+
test('Serializer restoreFromState', t => {
|
|
124
|
+
t.plan(1)
|
|
125
|
+
|
|
126
|
+
const Serializer = require('../lib/serializer')
|
|
127
|
+
const serializer = new Serializer()
|
|
128
|
+
const state = serializer.getState()
|
|
129
|
+
const restored = Serializer.restoreFromState(state)
|
|
130
|
+
t.assert.ok(restored instanceof Serializer)
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
test('Validator restoreFromState', t => {
|
|
134
|
+
t.plan(1)
|
|
135
|
+
|
|
136
|
+
const Validator = require('../lib/validator')
|
|
137
|
+
const validator = new Validator()
|
|
138
|
+
validator.addSchema({ type: 'string' }, 'test')
|
|
139
|
+
const state = validator.getState()
|
|
140
|
+
const restored = Validator.restoreFromState(state)
|
|
141
|
+
t.assert.ok(restored instanceof Validator)
|
|
142
|
+
})
|
|
@@ -466,3 +466,75 @@ test('external recursive if/then/else', (t) => {
|
|
|
466
466
|
const stringify = build(schema, { schema: { externalSchema } })
|
|
467
467
|
t.assert.equal(stringify(data), '{"a":{"base":"a","bar":"42"},"b":{"base":"b","baz":"43"}}')
|
|
468
468
|
})
|
|
469
|
+
|
|
470
|
+
test('invalid if-then-else schema', (t) => {
|
|
471
|
+
t.plan(1)
|
|
472
|
+
|
|
473
|
+
const schema = {
|
|
474
|
+
type: 'object',
|
|
475
|
+
if: {
|
|
476
|
+
type: 'object',
|
|
477
|
+
properties: {
|
|
478
|
+
kind: { type: 'string', enum: ['foobar'] }
|
|
479
|
+
}
|
|
480
|
+
},
|
|
481
|
+
then: {
|
|
482
|
+
type: 'object',
|
|
483
|
+
properties: {
|
|
484
|
+
foo: { type: 'string' }
|
|
485
|
+
}
|
|
486
|
+
},
|
|
487
|
+
else: {
|
|
488
|
+
type: 'object',
|
|
489
|
+
properties: 'invalid' // properties must be object
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
try {
|
|
494
|
+
build(schema)
|
|
495
|
+
t.assert.fail('Should throw')
|
|
496
|
+
} catch (err) {
|
|
497
|
+
t.assert.ok(err.message.includes('schema is invalid'))
|
|
498
|
+
}
|
|
499
|
+
})
|
|
500
|
+
|
|
501
|
+
test('if-then-else with allOf', (t) => {
|
|
502
|
+
t.plan(2)
|
|
503
|
+
|
|
504
|
+
const schema = {
|
|
505
|
+
type: 'object',
|
|
506
|
+
allOf: [
|
|
507
|
+
{
|
|
508
|
+
type: 'object',
|
|
509
|
+
properties: {
|
|
510
|
+
base: { type: 'string' }
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
],
|
|
514
|
+
if: {
|
|
515
|
+
type: 'object',
|
|
516
|
+
properties: {
|
|
517
|
+
kind: { type: 'string', enum: ['foobar'] }
|
|
518
|
+
}
|
|
519
|
+
},
|
|
520
|
+
then: {
|
|
521
|
+
type: 'object',
|
|
522
|
+
properties: {
|
|
523
|
+
foo: { type: 'string' }
|
|
524
|
+
}
|
|
525
|
+
},
|
|
526
|
+
else: {
|
|
527
|
+
type: 'object',
|
|
528
|
+
properties: {
|
|
529
|
+
bar: { type: 'string' }
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
const stringify = build(schema)
|
|
535
|
+
const data = { base: 'test', kind: 'foobar', foo: 'value' }
|
|
536
|
+
const output = stringify(data)
|
|
537
|
+
|
|
538
|
+
t.assert.doesNotThrow(() => JSON.parse(output))
|
|
539
|
+
t.assert.equal(output, '{"base":"test","foo":"value"}')
|
|
540
|
+
})
|
|
@@ -16,3 +16,23 @@ test('Should throw on invalid schema', t => {
|
|
|
16
16
|
})
|
|
17
17
|
}, { message: /^"invalid" schema is invalid:.*/ })
|
|
18
18
|
})
|
|
19
|
+
|
|
20
|
+
test('invalid not schema', (t) => {
|
|
21
|
+
t.plan(1)
|
|
22
|
+
|
|
23
|
+
const schema = {
|
|
24
|
+
type: 'object',
|
|
25
|
+
properties: {
|
|
26
|
+
prop: {
|
|
27
|
+
not: 'not object' // invalid, not must be object
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
try {
|
|
33
|
+
build(schema)
|
|
34
|
+
t.assert.fail('Should throw')
|
|
35
|
+
} catch (err) {
|
|
36
|
+
t.assert.ok(err.message.includes('schema is invalid'))
|
|
37
|
+
}
|
|
38
|
+
})
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { test } = require('node:test')
|
|
4
|
+
const build = require('..')
|
|
5
|
+
|
|
6
|
+
test('external $ref schema should be reused, not inlined at every reference', (t) => {
|
|
7
|
+
t.plan(3)
|
|
8
|
+
|
|
9
|
+
const contactSchema = {
|
|
10
|
+
$id: 'contact.json',
|
|
11
|
+
type: 'object',
|
|
12
|
+
properties: {
|
|
13
|
+
firstName: { type: 'string' },
|
|
14
|
+
lastName: { type: 'string' },
|
|
15
|
+
email: { type: 'string' }
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Schema referencing the same external $ref multiple times
|
|
20
|
+
const parentSchema = {
|
|
21
|
+
type: 'object',
|
|
22
|
+
properties: {
|
|
23
|
+
owner: { $ref: 'contact.json' },
|
|
24
|
+
assignee: { $ref: 'contact.json' },
|
|
25
|
+
reporter: { $ref: 'contact.json' }
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const serializer = build(parentSchema, { schema: { 'contact.json': contactSchema } })
|
|
30
|
+
const code = serializer.toString()
|
|
31
|
+
|
|
32
|
+
// The serialized function should contain extracted anonymous functions
|
|
33
|
+
// rather than inlining the full contact schema at every reference.
|
|
34
|
+
// With the fix, `firstName` appears 0 times in the main serializer body
|
|
35
|
+
// (it is inside the extracted function, which is not included in .toString())
|
|
36
|
+
// or at most 1 time if in a single extracted function.
|
|
37
|
+
const firstNameMatches = code.match(/firstName/g)
|
|
38
|
+
t.assert.ok(firstNameMatches === null || firstNameMatches.length <= 1,
|
|
39
|
+
`firstName should appear at most once (extracted function), got ${firstNameMatches ? firstNameMatches.length : 0}`)
|
|
40
|
+
|
|
41
|
+
// Verify correct serialization
|
|
42
|
+
const data = {
|
|
43
|
+
owner: { firstName: 'John', lastName: 'Doe', email: 'john@example.com' },
|
|
44
|
+
assignee: { firstName: 'Jane', lastName: 'Smith', email: 'jane@example.com' },
|
|
45
|
+
reporter: { firstName: 'Bob', lastName: 'Jones', email: 'bob@example.com' }
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const output = serializer(data)
|
|
49
|
+
t.assert.doesNotThrow(() => JSON.parse(output))
|
|
50
|
+
t.assert.equal(output, JSON.stringify(data))
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
test('external $ref schema reused with anyOf wrapping', (t) => {
|
|
54
|
+
t.plan(2)
|
|
55
|
+
|
|
56
|
+
const contactSchema = {
|
|
57
|
+
$id: 'contact.json',
|
|
58
|
+
type: 'object',
|
|
59
|
+
properties: {
|
|
60
|
+
firstName: { type: 'string' },
|
|
61
|
+
lastName: { type: 'string' },
|
|
62
|
+
email: { type: 'string' }
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Common pattern: anyOf wrapping $ref for polymorphic fields (populated object OR string ID OR null)
|
|
67
|
+
const parentSchema = {
|
|
68
|
+
type: 'object',
|
|
69
|
+
properties: {
|
|
70
|
+
owner: {
|
|
71
|
+
anyOf: [{ type: ['string', 'null'] }, { $ref: 'contact.json' }]
|
|
72
|
+
},
|
|
73
|
+
assignee: {
|
|
74
|
+
anyOf: [{ type: ['string', 'null'] }, { $ref: 'contact.json' }]
|
|
75
|
+
},
|
|
76
|
+
reporter: {
|
|
77
|
+
anyOf: [{ type: ['string', 'null'] }, { $ref: 'contact.json' }]
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const serializer = build(parentSchema, { schema: { 'contact.json': contactSchema } })
|
|
83
|
+
|
|
84
|
+
// Serialize with populated objects
|
|
85
|
+
const data = {
|
|
86
|
+
owner: { firstName: 'John', lastName: 'Doe', email: 'john@example.com' },
|
|
87
|
+
assignee: { firstName: 'Jane', lastName: 'Smith', email: 'jane@example.com' },
|
|
88
|
+
reporter: { firstName: 'Bob', lastName: 'Jones', email: 'bob@example.com' }
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const output = serializer(data)
|
|
92
|
+
t.assert.doesNotThrow(() => JSON.parse(output))
|
|
93
|
+
t.assert.equal(output, JSON.stringify(data))
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
test('external $ref schema reused in array items', (t) => {
|
|
97
|
+
t.plan(2)
|
|
98
|
+
|
|
99
|
+
const contactSchema = {
|
|
100
|
+
$id: 'contact.json',
|
|
101
|
+
type: 'object',
|
|
102
|
+
properties: {
|
|
103
|
+
firstName: { type: 'string' },
|
|
104
|
+
lastName: { type: 'string' }
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const parentSchema = {
|
|
109
|
+
type: 'object',
|
|
110
|
+
properties: {
|
|
111
|
+
contacts: {
|
|
112
|
+
type: 'array',
|
|
113
|
+
items: { $ref: 'contact.json' }
|
|
114
|
+
},
|
|
115
|
+
primary: { $ref: 'contact.json' },
|
|
116
|
+
secondary: { $ref: 'contact.json' }
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const serializer = build(parentSchema, { schema: { 'contact.json': contactSchema } })
|
|
121
|
+
|
|
122
|
+
const data = {
|
|
123
|
+
contacts: [
|
|
124
|
+
{ firstName: 'Alice', lastName: 'Wonder' },
|
|
125
|
+
{ firstName: 'Bob', lastName: 'Builder' }
|
|
126
|
+
],
|
|
127
|
+
primary: { firstName: 'Charlie', lastName: 'Charm' },
|
|
128
|
+
secondary: { firstName: 'Diana', lastName: 'Prince' }
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const output = serializer(data)
|
|
132
|
+
t.assert.doesNotThrow(() => JSON.parse(output))
|
|
133
|
+
t.assert.equal(output, JSON.stringify(data))
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
test('external array $ref schema should be reused, not inlined at every reference', (t) => {
|
|
137
|
+
t.plan(2)
|
|
138
|
+
|
|
139
|
+
const tagsSchema = {
|
|
140
|
+
$id: 'tags.json',
|
|
141
|
+
type: 'array',
|
|
142
|
+
items: { type: 'string' }
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const parentSchema = {
|
|
146
|
+
type: 'object',
|
|
147
|
+
properties: {
|
|
148
|
+
a: { $ref: 'tags.json' },
|
|
149
|
+
b: { $ref: 'tags.json' },
|
|
150
|
+
c: { $ref: 'tags.json' }
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const serializer = build(parentSchema, { schema: { 'tags.json': tagsSchema } })
|
|
155
|
+
|
|
156
|
+
const data = {
|
|
157
|
+
a: ['x', 'y'],
|
|
158
|
+
b: ['z'],
|
|
159
|
+
c: []
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const output = serializer(data)
|
|
163
|
+
t.assert.doesNotThrow(() => JSON.parse(output))
|
|
164
|
+
t.assert.equal(output, JSON.stringify(data))
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
test('inline anonymous schemas should still be inlined (not extracted)', (t) => {
|
|
168
|
+
t.plan(1)
|
|
169
|
+
|
|
170
|
+
// Inline schemas (no external $ref) should continue to work normally
|
|
171
|
+
const schema = {
|
|
172
|
+
type: 'object',
|
|
173
|
+
properties: {
|
|
174
|
+
a: { type: 'object', properties: { x: { type: 'string' } } },
|
|
175
|
+
b: { type: 'object', properties: { x: { type: 'string' } } }
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const serializer = build(schema)
|
|
180
|
+
const output = serializer({ a: { x: 'hello' }, b: { x: 'world' } })
|
|
181
|
+
|
|
182
|
+
t.assert.equal(output, '{"a":{"x":"hello"},"b":{"x":"world"}}')
|
|
183
|
+
})
|
|
@@ -1,57 +1,57 @@
|
|
|
1
|
-
'use strict'
|
|
2
|
-
|
|
3
|
-
const { test } = require('node:test')
|
|
4
|
-
const build = require('..')
|
|
5
|
-
|
|
6
|
-
test('additionalProperties: false', (t) => {
|
|
7
|
-
t.plan(1)
|
|
8
|
-
const stringify = build({
|
|
9
|
-
title: 'additionalProperties',
|
|
10
|
-
type: 'object',
|
|
11
|
-
properties: {
|
|
12
|
-
foo: {
|
|
13
|
-
type: 'string'
|
|
14
|
-
}
|
|
15
|
-
},
|
|
16
|
-
additionalProperties: false
|
|
17
|
-
})
|
|
18
|
-
|
|
19
|
-
const obj = { foo: 'a', bar: 'b', baz: 'c' }
|
|
20
|
-
t.assert.equal(stringify(obj), '{"foo":"a"}')
|
|
21
|
-
})
|
|
22
|
-
|
|
23
|
-
test('additionalProperties: {}', (t) => {
|
|
24
|
-
t.plan(1)
|
|
25
|
-
const stringify = build({
|
|
26
|
-
title: 'additionalProperties',
|
|
27
|
-
type: 'object',
|
|
28
|
-
properties: {
|
|
29
|
-
foo: {
|
|
30
|
-
type: 'string'
|
|
31
|
-
}
|
|
32
|
-
},
|
|
33
|
-
additionalProperties: {}
|
|
34
|
-
})
|
|
35
|
-
|
|
36
|
-
const obj = { foo: 'a', bar: 'b', baz: 'c' }
|
|
37
|
-
t.assert.equal(stringify(obj), '{"foo":"a","bar":"b","baz":"c"}')
|
|
38
|
-
})
|
|
39
|
-
|
|
40
|
-
test('additionalProperties: {type: string}', (t) => {
|
|
41
|
-
t.plan(1)
|
|
42
|
-
const stringify = build({
|
|
43
|
-
title: 'additionalProperties',
|
|
44
|
-
type: 'object',
|
|
45
|
-
properties: {
|
|
46
|
-
foo: {
|
|
47
|
-
type: 'string'
|
|
48
|
-
}
|
|
49
|
-
},
|
|
50
|
-
additionalProperties: {
|
|
51
|
-
type: 'string'
|
|
52
|
-
}
|
|
53
|
-
})
|
|
54
|
-
|
|
55
|
-
const obj = { foo: 'a', bar: 'b', baz: 'c' }
|
|
56
|
-
t.assert.equal(stringify(obj), '{"foo":"a","bar":"b","baz":"c"}')
|
|
57
|
-
})
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { test } = require('node:test')
|
|
4
|
+
const build = require('..')
|
|
5
|
+
|
|
6
|
+
test('additionalProperties: false', (t) => {
|
|
7
|
+
t.plan(1)
|
|
8
|
+
const stringify = build({
|
|
9
|
+
title: 'additionalProperties',
|
|
10
|
+
type: 'object',
|
|
11
|
+
properties: {
|
|
12
|
+
foo: {
|
|
13
|
+
type: 'string'
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
additionalProperties: false
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
const obj = { foo: 'a', bar: 'b', baz: 'c' }
|
|
20
|
+
t.assert.equal(stringify(obj), '{"foo":"a"}')
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
test('additionalProperties: {}', (t) => {
|
|
24
|
+
t.plan(1)
|
|
25
|
+
const stringify = build({
|
|
26
|
+
title: 'additionalProperties',
|
|
27
|
+
type: 'object',
|
|
28
|
+
properties: {
|
|
29
|
+
foo: {
|
|
30
|
+
type: 'string'
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
additionalProperties: {}
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
const obj = { foo: 'a', bar: 'b', baz: 'c' }
|
|
37
|
+
t.assert.equal(stringify(obj), '{"foo":"a","bar":"b","baz":"c"}')
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
test('additionalProperties: {type: string}', (t) => {
|
|
41
|
+
t.plan(1)
|
|
42
|
+
const stringify = build({
|
|
43
|
+
title: 'additionalProperties',
|
|
44
|
+
type: 'object',
|
|
45
|
+
properties: {
|
|
46
|
+
foo: {
|
|
47
|
+
type: 'string'
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
additionalProperties: {
|
|
51
|
+
type: 'string'
|
|
52
|
+
}
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
const obj = { foo: 'a', bar: 'b', baz: 'c' }
|
|
56
|
+
t.assert.equal(stringify(obj), '{"foo":"a","bar":"b","baz":"c"}')
|
|
57
|
+
})
|
package/test/oneof.test.js
CHANGED
|
@@ -488,3 +488,23 @@ test('all array items does not match oneOf types', (t) => {
|
|
|
488
488
|
|
|
489
489
|
t.assert.throws(() => stringify({ data: [null, false, true, undefined, [], {}] }))
|
|
490
490
|
})
|
|
491
|
+
|
|
492
|
+
test('invalid oneOf schema', (t) => {
|
|
493
|
+
t.plan(1)
|
|
494
|
+
|
|
495
|
+
const schema = {
|
|
496
|
+
type: 'object',
|
|
497
|
+
properties: {
|
|
498
|
+
prop: {
|
|
499
|
+
oneOf: 'not array' // invalid, oneOf must be array
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
try {
|
|
505
|
+
build(schema)
|
|
506
|
+
t.assert.fail('Should throw')
|
|
507
|
+
} catch (err) {
|
|
508
|
+
t.assert.ok(err.message.includes('schema is invalid'))
|
|
509
|
+
}
|
|
510
|
+
})
|
package/test/ref.test.js
CHANGED
|
@@ -2044,3 +2044,34 @@ test('ref internal - throw if schema has definition twice with different shape',
|
|
|
2044
2044
|
t.assert.equal(err.message, 'There is already another anchor "#uri" in schema "test".')
|
|
2045
2045
|
}
|
|
2046
2046
|
})
|
|
2047
|
+
|
|
2048
|
+
test('ref nested', (t) => {
|
|
2049
|
+
t.plan(2)
|
|
2050
|
+
|
|
2051
|
+
const schema = {
|
|
2052
|
+
definitions: {
|
|
2053
|
+
def1: {
|
|
2054
|
+
$ref: '#/definitions/def2'
|
|
2055
|
+
},
|
|
2056
|
+
def2: {
|
|
2057
|
+
type: 'string'
|
|
2058
|
+
}
|
|
2059
|
+
},
|
|
2060
|
+
type: 'object',
|
|
2061
|
+
properties: {
|
|
2062
|
+
str: {
|
|
2063
|
+
$ref: '#/definitions/def1'
|
|
2064
|
+
}
|
|
2065
|
+
}
|
|
2066
|
+
}
|
|
2067
|
+
|
|
2068
|
+
const object = {
|
|
2069
|
+
str: 'test'
|
|
2070
|
+
}
|
|
2071
|
+
|
|
2072
|
+
const stringify = build(schema)
|
|
2073
|
+
const output = stringify(object)
|
|
2074
|
+
|
|
2075
|
+
t.assert.doesNotThrow(() => JSON.parse(output))
|
|
2076
|
+
t.assert.equal(output, '{"str":"test"}')
|
|
2077
|
+
})
|
package/test/typebox.test.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars -- Test using this disabled, see https://github.com/fastify/fast-json-stringify/pull/683
|
|
2
2
|
import Ajv from 'ajv'
|
|
3
3
|
import build, { restore, Schema, validLargeArrayMechanisms } from '..'
|
|
4
|
-
import {
|
|
4
|
+
import { expect } from 'tstyche'
|
|
5
5
|
|
|
6
6
|
// Number schemas
|
|
7
7
|
build({
|
|
@@ -26,9 +26,10 @@ build({
|
|
|
26
26
|
build({
|
|
27
27
|
type: 'number'
|
|
28
28
|
}, { rounding: 'trunc' })
|
|
29
|
-
|
|
29
|
+
|
|
30
|
+
expect(build).type.not.toBeCallableWith({
|
|
30
31
|
type: 'number'
|
|
31
|
-
}, { rounding: 'invalid' })
|
|
32
|
+
}, { rounding: 'invalid' })
|
|
32
33
|
|
|
33
34
|
// String schema
|
|
34
35
|
build({
|
|
@@ -153,7 +154,6 @@ build({
|
|
|
153
154
|
})({ something: 'a string', somethingElse: 42 })
|
|
154
155
|
|
|
155
156
|
// String schema with format
|
|
156
|
-
|
|
157
157
|
build({
|
|
158
158
|
type: 'string',
|
|
159
159
|
format: 'date-time'
|
|
@@ -181,7 +181,6 @@ str = build({
|
|
|
181
181
|
type: 'number'
|
|
182
182
|
}, { mode: 'standalone' })
|
|
183
183
|
*/
|
|
184
|
-
|
|
185
184
|
const debugCompiled = build({
|
|
186
185
|
title: 'default string',
|
|
187
186
|
type: 'object',
|
|
@@ -191,11 +190,12 @@ const debugCompiled = build({
|
|
|
191
190
|
}
|
|
192
191
|
}
|
|
193
192
|
}, { mode: 'debug' })
|
|
194
|
-
expectType<ReturnType<typeof build>>(build.restore(debugCompiled))
|
|
195
|
-
expectType<ReturnType<typeof build>>(restore(debugCompiled))
|
|
196
193
|
|
|
197
|
-
|
|
198
|
-
|
|
194
|
+
expect(build.restore(debugCompiled)).type.toBe(build({} as Schema))
|
|
195
|
+
expect(restore(debugCompiled)).type.toBe(build({} as Schema))
|
|
196
|
+
|
|
197
|
+
expect(build.validLargeArrayMechanisms).type.toBe<string[]>()
|
|
198
|
+
expect(validLargeArrayMechanisms).type.toBe<string[]>()
|
|
199
199
|
|
|
200
200
|
/**
|
|
201
201
|
* Schema inference
|
|
@@ -211,10 +211,11 @@ const stringify3 = build({
|
|
|
211
211
|
type: 'object',
|
|
212
212
|
properties: { a: { type: 'string' } },
|
|
213
213
|
})
|
|
214
|
+
|
|
214
215
|
stringify3<InferenceSchema>({ id: '123' })
|
|
215
216
|
stringify3<InferenceSchema>({ a: 123, id: '123' })
|
|
216
|
-
|
|
217
|
-
|
|
217
|
+
expect(stringify3<InferenceSchema>).type.not.toBeCallableWith({ anotherOne: 'bar' })
|
|
218
|
+
expect(stringify3<Schema>).type.not.toBeCallableWith({ a: 'bar' })
|
|
218
219
|
|
|
219
220
|
// Without inference
|
|
220
221
|
const stringify4 = build({
|
|
@@ -231,29 +232,28 @@ const stringify5 = build({
|
|
|
231
232
|
type: 'string',
|
|
232
233
|
})
|
|
233
234
|
stringify5('foo')
|
|
234
|
-
|
|
235
|
+
expect(stringify5).type.not.toBeCallableWith({ id: '123' })
|
|
235
236
|
|
|
236
237
|
// Without inference - null type
|
|
237
238
|
const stringify6 = build({
|
|
238
239
|
type: 'null',
|
|
239
240
|
})
|
|
240
241
|
stringify6(null)
|
|
241
|
-
|
|
242
|
+
expect(stringify6).type.not.toBeCallableWith('a string')
|
|
242
243
|
|
|
243
244
|
// Without inference - boolean type
|
|
244
245
|
const stringify7 = build({
|
|
245
246
|
type: 'boolean',
|
|
246
247
|
})
|
|
247
248
|
stringify7(true)
|
|
248
|
-
|
|
249
|
+
expect(stringify7).type.not.toBeCallableWith('a string')
|
|
249
250
|
|
|
250
251
|
// largeArrayMechanism
|
|
251
|
-
|
|
252
252
|
build({}, { largeArrayMechanism: 'json-stringify' })
|
|
253
253
|
build({}, { largeArrayMechanism: 'default' })
|
|
254
|
-
|
|
254
|
+
expect(build).type.not.toBeCallableWith({} as Schema, { largeArrayMechanism: 'invalid' })
|
|
255
255
|
|
|
256
256
|
build({}, { largeArraySize: 2000 })
|
|
257
257
|
build({}, { largeArraySize: '2e4' })
|
|
258
258
|
build({}, { largeArraySize: 2n })
|
|
259
|
-
|
|
259
|
+
expect(build).type.not.toBeCallableWith({} as Schema, { largeArraySize: ['asdf'] })
|
package/.github/stale.yml
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
# Number of days of inactivity before an issue becomes stale
|
|
2
|
-
daysUntilStale: 15
|
|
3
|
-
# Number of days of inactivity before a stale issue is closed
|
|
4
|
-
daysUntilClose: 7
|
|
5
|
-
# Issues with these labels will never be considered stale
|
|
6
|
-
exemptLabels:
|
|
7
|
-
- "discussion"
|
|
8
|
-
- "feature request"
|
|
9
|
-
- "bug"
|
|
10
|
-
- "help wanted"
|
|
11
|
-
- "plugin suggestion"
|
|
12
|
-
- "good first issue"
|
|
13
|
-
# Label to use when marking an issue as stale
|
|
14
|
-
staleLabel: stale
|
|
15
|
-
# Comment to post when marking an issue as stale. Set to `false` to disable
|
|
16
|
-
markComment: >
|
|
17
|
-
This issue has been automatically marked as stale because it has not had
|
|
18
|
-
recent activity. It will be closed if no further activity occurs. Thank you
|
|
19
|
-
for your contributions.
|
|
20
|
-
# Comment to post when closing a stale issue. Set to `false` to disable
|
|
21
|
-
closeComment: false
|