fast-json-stringify 7.0.1 → 7.1.1
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/dependabot.yml +1 -0
- package/.github/workflows/ci.yml +1 -1
- package/.github/workflows/lock-threads.yml +1 -1
- package/.github/workflows/release.yml +24 -0
- package/README.md +20 -3
- package/benchmark/bench-cmp-lib.js +63 -38
- package/benchmark/bench-thread.js +8 -2
- package/benchmark/bench.js +86 -0
- package/benchmark/fixtures/api-responses.js +266 -0
- package/index.js +366 -98
- package/lib/location.js +3 -1
- package/lib/serializer.js +41 -37
- package/lib/standalone.js +22 -3
- package/lib/validator.js +22 -3
- package/package.json +4 -5
- package/test/additionalProperties.test.js +40 -0
- package/test/anyof.test.js +42 -0
- package/test/array.test.js +26 -1
- package/test/basic.test.js +1 -1
- package/test/code-generation-fallbacks.test.js +158 -0
- package/test/compile-validators.test.js +110 -0
- package/test/const.test.js +7 -6
- package/test/date.test.js +28 -1
- package/test/debug-mode.test.js +8 -0
- package/test/deopt.test.js +251 -0
- package/test/if-then-else.test.js +145 -0
- package/test/inferType.test.js +1 -1
- package/test/integer.test.js +1 -1
- package/test/oneof.test.js +242 -0
- package/test/optional-property-prefix.test.js +119 -0
- package/test/property-names.test.js +41 -0
- package/test/regex.test.js +1 -1
- package/test/schema-depth.test.js +227 -0
- package/test/standalone-mode.test.js +109 -2
- package/test/string.test.js +8 -0
- package/test/surrogate.test.js +1 -1
- package/test/typesArray.test.js +59 -0
- package/test/utils/validator.js +20 -0
- package/types/index.d.ts +20 -0
- package/types/index.tst.ts +13 -0
package/.github/dependabot.yml
CHANGED
package/.github/workflows/ci.yml
CHANGED
|
@@ -27,7 +27,7 @@ jobs:
|
|
|
27
27
|
permissions:
|
|
28
28
|
contents: write
|
|
29
29
|
pull-requests: write
|
|
30
|
-
uses: fastify/workflows/.github/workflows/plugins-ci.yml@
|
|
30
|
+
uses: fastify/workflows/.github/workflows/plugins-ci.yml@ef591e2186785d5ab36b9fe6a79c7ce2f1d94e57 # v7.0.0
|
|
31
31
|
with:
|
|
32
32
|
license-check: true
|
|
33
33
|
lint: true
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
name: Release the package
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
workflow_dispatch:
|
|
5
|
+
inputs:
|
|
6
|
+
semver:
|
|
7
|
+
description: 'Release bump type'
|
|
8
|
+
required: true
|
|
9
|
+
type: choice
|
|
10
|
+
options:
|
|
11
|
+
- patch
|
|
12
|
+
- minor
|
|
13
|
+
- major
|
|
14
|
+
|
|
15
|
+
permissions: {}
|
|
16
|
+
|
|
17
|
+
jobs:
|
|
18
|
+
release:
|
|
19
|
+
permissions:
|
|
20
|
+
id-token: write # required for npm provenance via OIDC
|
|
21
|
+
contents: write # required to push the release commit and the tag
|
|
22
|
+
uses: fastify/workflows/.github/workflows/reusable-release.yml@b1a11bce713bc857e52b2231fec3c89071ad3135
|
|
23
|
+
with:
|
|
24
|
+
semver: ${{ inputs.semver }}
|
package/README.md
CHANGED
|
@@ -121,15 +121,24 @@ const fastJson = require('fast-json-stringify')
|
|
|
121
121
|
const stringify = fastJson(mySchema, {
|
|
122
122
|
schema: { ... },
|
|
123
123
|
ajv: { ... },
|
|
124
|
-
rounding: 'ceil'
|
|
124
|
+
rounding: 'ceil',
|
|
125
|
+
maxDepth: 100
|
|
125
126
|
})
|
|
126
127
|
```
|
|
127
128
|
|
|
128
129
|
- `schema`: external schemas references by $ref property. [More details](#ref)
|
|
129
130
|
- `ajv`: [ajv v8 instance's settings](https://ajv.js.org/options.html) for those properties that require `ajv`. [More details](#anyof)
|
|
130
131
|
- `rounding`: setup how the `integer` types will be rounded when not integers. [More details](#integer)
|
|
132
|
+
- `maxDepth`: maximum number of nested schema levels allowed during compilation.
|
|
133
|
+
Defaults to `100` and may be set from `0` to `100`. Schemas that exceed this limit
|
|
134
|
+
are rejected before compilation.
|
|
135
|
+
- `inlineValidators`: when using standalone mode, embed Ajv-generated validator functions in the output instead of compiling schemas at runtime. [More details](#standalone)
|
|
131
136
|
- `largeArrayMechanism`: set the mechanism that should be used to handle large
|
|
132
137
|
(by default `20000` or more items) arrays. [More details](#largearrays)
|
|
138
|
+
- `compileValidators`: when `true`, the `ajv` validators used by `anyOf`, `oneOf` and
|
|
139
|
+
`if/then/else` are compiled during `build()` instead of lazily, on the first serialization
|
|
140
|
+
that reaches them. This makes `build()` slower but removes a potentially large one-off cost
|
|
141
|
+
from the first `stringify()` call. Defaults to `false`.
|
|
133
142
|
|
|
134
143
|
|
|
135
144
|
<a name="api"></a>
|
|
@@ -168,7 +177,7 @@ And nested ones, too.
|
|
|
168
177
|
| `date` | `2020-04-03` |
|
|
169
178
|
| `time` | `09:11:08` |
|
|
170
179
|
|
|
171
|
-
**Note**: In the case of a string formatted Date and not Date Object,
|
|
180
|
+
**Note**: In the case of a string formatted Date and not Date Object, the value is not reformatted nor validated: it should already be properly formatted. It is only escaped, so that the resulting document is always valid JSON.
|
|
172
181
|
|
|
173
182
|
Example with a Date object:
|
|
174
183
|
|
|
@@ -724,13 +733,21 @@ const code = fastJson({
|
|
|
724
733
|
type: 'string'
|
|
725
734
|
}
|
|
726
735
|
}
|
|
727
|
-
}, { mode: 'standalone' })
|
|
736
|
+
}, { mode: 'standalone', inlineValidators: true })
|
|
728
737
|
|
|
729
738
|
fs.writeFileSync('stringify.js', code)
|
|
730
739
|
const stringify = require('stringify.js')
|
|
731
740
|
console.log(stringify({ firstName: 'Foo', surname: 'bar' })) // '{"firstName":"Foo"}'
|
|
732
741
|
```
|
|
733
742
|
|
|
743
|
+
Set `inlineValidators` to `true` to include the Ajv-generated functions used by
|
|
744
|
+
`anyOf`, `oneOf`, and `if/then/else` in the same output file. This avoids
|
|
745
|
+
rebuilding an Ajv instance and compiling their schemas at runtime. The
|
|
746
|
+
generated module still requires `fast-json-stringify` for its serializer and
|
|
747
|
+
may require Ajv runtime helpers used by the generated validation code. Custom
|
|
748
|
+
Ajv formats used by these schemas must support Ajv's standalone code
|
|
749
|
+
generation.
|
|
750
|
+
|
|
734
751
|
<a name="acknowledgments"></a>
|
|
735
752
|
## Acknowledgments
|
|
736
753
|
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
+
const assert = require('node:assert/strict')
|
|
3
4
|
const { Bench } = require('tinybench')
|
|
5
|
+
const apiResponses = require('./fixtures/api-responses')
|
|
4
6
|
const suite = new Bench({
|
|
5
7
|
name: 'Library Comparison Benchmarks',
|
|
6
8
|
setup: (_task, mode) => {
|
|
@@ -173,139 +175,162 @@ for (let i = 0; i < MULTI_ARRAY_LENGTH; i++) {
|
|
|
173
175
|
multiArray[i] = obj
|
|
174
176
|
}
|
|
175
177
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
+
// Some serializers return unflattened strings. Fastify calls Buffer.byteLength()
|
|
179
|
+
// for Content-Length, which triggers flattening; include that cost in every case.
|
|
180
|
+
suite.add('fast-json-stringify: creation and first stringify', function () {
|
|
181
|
+
Buffer.byteLength(FJS(schema)(obj))
|
|
178
182
|
})
|
|
179
|
-
suite.add('compile-json-stringify: creation', function () {
|
|
180
|
-
CJS(schemaCJS)
|
|
183
|
+
suite.add('compile-json-stringify: creation and first stringify', function () {
|
|
184
|
+
Buffer.byteLength(CJS(schemaCJS)(obj))
|
|
181
185
|
})
|
|
182
|
-
suite.add('AJV: creation', function () {
|
|
183
|
-
ajv.compileSerializer(schemaAJVJTD)
|
|
186
|
+
suite.add('AJV: creation and first stringify', function () {
|
|
187
|
+
Buffer.byteLength(ajv.compileSerializer(schemaAJVJTD)(obj))
|
|
184
188
|
})
|
|
185
|
-
suite.add('json-accelerator: creation', function () {
|
|
186
|
-
createAccelerator(schema)
|
|
189
|
+
suite.add('json-accelerator: creation and first stringify', function () {
|
|
190
|
+
Buffer.byteLength(createAccelerator(schema)(obj))
|
|
187
191
|
})
|
|
188
192
|
|
|
189
193
|
suite.add('JSON.stringify: array', function () {
|
|
190
|
-
JSON.stringify(multiArray)
|
|
194
|
+
Buffer.byteLength(JSON.stringify(multiArray))
|
|
191
195
|
})
|
|
192
196
|
|
|
193
197
|
suite.add('fast-json-stringify [default]: array', function () {
|
|
194
|
-
stringifyArrayDefault(multiArray)
|
|
198
|
+
Buffer.byteLength(stringifyArrayDefault(multiArray))
|
|
195
199
|
})
|
|
196
200
|
|
|
197
201
|
suite.add('json-accelerator: array', function () {
|
|
198
|
-
accelArray(multiArray)
|
|
202
|
+
Buffer.byteLength(accelArray(multiArray))
|
|
199
203
|
})
|
|
200
204
|
|
|
201
205
|
suite.add('fast-json-stringify [json-stringify]: array', function () {
|
|
202
|
-
stringifyArrayJSONStringify(multiArray)
|
|
206
|
+
Buffer.byteLength(stringifyArrayJSONStringify(multiArray))
|
|
203
207
|
})
|
|
204
208
|
|
|
205
209
|
suite.add('compile-json-stringify: array', function () {
|
|
206
|
-
CJSStringifyArray(multiArray)
|
|
210
|
+
Buffer.byteLength(CJSStringifyArray(multiArray))
|
|
207
211
|
})
|
|
208
212
|
|
|
209
213
|
suite.add('AJV: array', function () {
|
|
210
|
-
ajvSerializeArray(multiArray)
|
|
214
|
+
Buffer.byteLength(ajvSerializeArray(multiArray))
|
|
211
215
|
})
|
|
212
216
|
|
|
213
217
|
suite.add('JSON.stringify: large array', function () {
|
|
214
|
-
JSON.stringify(largeArray)
|
|
218
|
+
Buffer.byteLength(JSON.stringify(largeArray))
|
|
215
219
|
})
|
|
216
220
|
|
|
217
221
|
suite.add('fast-json-stringify [default]: large array', function () {
|
|
218
|
-
stringifyArrayDefault(largeArray)
|
|
222
|
+
Buffer.byteLength(stringifyArrayDefault(largeArray))
|
|
219
223
|
})
|
|
220
224
|
|
|
221
225
|
suite.add('fast-json-stringify [json-stringify]: large array', function () {
|
|
222
|
-
stringifyArrayJSONStringify(largeArray)
|
|
226
|
+
Buffer.byteLength(stringifyArrayJSONStringify(largeArray))
|
|
223
227
|
})
|
|
224
228
|
|
|
225
229
|
suite.add('compile-json-stringify: large array', function () {
|
|
226
|
-
CJSStringifyArray(largeArray)
|
|
230
|
+
Buffer.byteLength(CJSStringifyArray(largeArray))
|
|
227
231
|
})
|
|
228
232
|
|
|
229
233
|
suite.add('AJV: large array', function () {
|
|
230
|
-
ajvSerializeArray(largeArray)
|
|
234
|
+
Buffer.byteLength(ajvSerializeArray(largeArray))
|
|
231
235
|
})
|
|
232
236
|
|
|
233
237
|
suite.add('JSON.stringify: long string', function () {
|
|
234
|
-
JSON.stringify(str)
|
|
238
|
+
Buffer.byteLength(JSON.stringify(str))
|
|
235
239
|
})
|
|
236
240
|
|
|
237
241
|
suite.add('fast-json-stringify: long string', function () {
|
|
238
|
-
stringifyString(str)
|
|
242
|
+
Buffer.byteLength(stringifyString(str))
|
|
239
243
|
})
|
|
240
244
|
|
|
241
245
|
suite.add('json-accelerator: long string', function () {
|
|
242
|
-
stringifyString(str)
|
|
246
|
+
Buffer.byteLength(stringifyString(str))
|
|
243
247
|
})
|
|
244
248
|
|
|
245
249
|
suite.add('compile-json-stringify: long string', function () {
|
|
246
|
-
CJSStringifyString(str)
|
|
250
|
+
Buffer.byteLength(CJSStringifyString(str))
|
|
247
251
|
})
|
|
248
252
|
|
|
249
253
|
suite.add('AJV: long string', function () {
|
|
250
|
-
ajvSerializeString(str)
|
|
254
|
+
Buffer.byteLength(ajvSerializeString(str))
|
|
251
255
|
})
|
|
252
256
|
|
|
253
257
|
suite.add('JSON.stringify: short string', function () {
|
|
254
|
-
JSON.stringify('hello world')
|
|
258
|
+
Buffer.byteLength(JSON.stringify('hello world'))
|
|
255
259
|
})
|
|
256
260
|
|
|
257
261
|
suite.add('fast-json-stringify: short string', function () {
|
|
258
|
-
stringifyString('hello world')
|
|
262
|
+
Buffer.byteLength(stringifyString('hello world'))
|
|
259
263
|
})
|
|
260
264
|
|
|
261
265
|
suite.add('json-accelerator: short string', function () {
|
|
262
|
-
accelString('hello world')
|
|
266
|
+
Buffer.byteLength(accelString('hello world'))
|
|
263
267
|
})
|
|
264
268
|
|
|
265
269
|
suite.add('compile-json-stringify: short string', function () {
|
|
266
|
-
CJSStringifyString('hello world')
|
|
270
|
+
Buffer.byteLength(CJSStringifyString('hello world'))
|
|
267
271
|
})
|
|
268
272
|
|
|
269
273
|
suite.add('AJV: short string', function () {
|
|
270
|
-
ajvSerializeString('hello world')
|
|
274
|
+
Buffer.byteLength(ajvSerializeString('hello world'))
|
|
271
275
|
})
|
|
272
276
|
|
|
273
277
|
suite.add('JSON.stringify: obj', function () {
|
|
274
|
-
JSON.stringify(obj)
|
|
278
|
+
Buffer.byteLength(JSON.stringify(obj))
|
|
275
279
|
})
|
|
276
280
|
|
|
277
281
|
suite.add('fast-json-stringify: obj', function () {
|
|
278
|
-
stringify(obj)
|
|
282
|
+
Buffer.byteLength(stringify(obj))
|
|
279
283
|
})
|
|
280
284
|
|
|
281
285
|
suite.add('json-accelerator: obj', function () {
|
|
282
|
-
accelStringify(obj)
|
|
286
|
+
Buffer.byteLength(accelStringify(obj))
|
|
283
287
|
})
|
|
284
288
|
|
|
285
289
|
suite.add('compile-json-stringify: obj', function () {
|
|
286
|
-
CJSStringify(obj)
|
|
290
|
+
Buffer.byteLength(CJSStringify(obj))
|
|
287
291
|
})
|
|
288
292
|
|
|
289
293
|
suite.add('AJV: obj', function () {
|
|
290
|
-
ajvSerialize(obj)
|
|
294
|
+
Buffer.byteLength(ajvSerialize(obj))
|
|
291
295
|
})
|
|
292
296
|
|
|
293
297
|
suite.add('JSON.stringify: date', function () {
|
|
294
|
-
JSON.stringify(date)
|
|
298
|
+
Buffer.byteLength(JSON.stringify(date))
|
|
295
299
|
})
|
|
296
300
|
|
|
297
301
|
suite.add('fast-json-stringify: date', function () {
|
|
298
|
-
stringifyDate(date)
|
|
302
|
+
Buffer.byteLength(stringifyDate(date))
|
|
299
303
|
})
|
|
300
304
|
|
|
301
305
|
suite.add('json-accelerate: date', function () {
|
|
302
|
-
accelDate(date)
|
|
306
|
+
Buffer.byteLength(accelDate(date))
|
|
303
307
|
})
|
|
304
308
|
|
|
305
309
|
suite.add('compile-json-stringify: date', function () {
|
|
306
|
-
CJSStringifyDate(date)
|
|
310
|
+
Buffer.byteLength(CJSStringifyDate(date))
|
|
307
311
|
})
|
|
308
312
|
|
|
313
|
+
for (const { name, schema: responseSchema, input } of apiResponses) {
|
|
314
|
+
const stringifyResponse = FJS(responseSchema)
|
|
315
|
+
// Check outside the timed loop that the schema preserves the complete response.
|
|
316
|
+
assert.deepStrictEqual(JSON.parse(stringifyResponse(input)), JSON.parse(JSON.stringify(input)))
|
|
317
|
+
|
|
318
|
+
suite.add(`JSON.stringify: ${name}`, function () {
|
|
319
|
+
Buffer.byteLength(JSON.stringify(input))
|
|
320
|
+
})
|
|
321
|
+
suite.add(`fast-json-stringify: ${name}`, function () {
|
|
322
|
+
Buffer.byteLength(stringifyResponse(input))
|
|
323
|
+
})
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
if (process.argv.includes('--fjs-vs-json')) {
|
|
327
|
+
for (const task of suite.tasks) {
|
|
328
|
+
if (!task.name.startsWith('fast-json-stringify') && !task.name.startsWith('JSON.stringify:')) {
|
|
329
|
+
suite.remove(task.name)
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
309
334
|
suite.run().then(() => {
|
|
310
335
|
const results = suite.tasks.map(task => ({
|
|
311
336
|
name: task.name,
|
|
@@ -15,10 +15,16 @@ const bench = new Bench({
|
|
|
15
15
|
})
|
|
16
16
|
|
|
17
17
|
const FJS = require('..')
|
|
18
|
-
const stringify = FJS(benchmark.schema)
|
|
18
|
+
const stringify = benchmark.compile ? null : FJS(benchmark.schema, benchmark.options)
|
|
19
19
|
|
|
20
20
|
bench.add(benchmark.name, () => {
|
|
21
|
-
|
|
21
|
+
// Some serializers return unflattened strings. Fastify calls Buffer.byteLength()
|
|
22
|
+
// for Content-Length, which triggers flattening; include that cost here too.
|
|
23
|
+
if (benchmark.compile) {
|
|
24
|
+
Buffer.byteLength(FJS(benchmark.schema, benchmark.options)(benchmark.input))
|
|
25
|
+
} else {
|
|
26
|
+
Buffer.byteLength(stringify(benchmark.input))
|
|
27
|
+
}
|
|
22
28
|
}).run().then(() => {
|
|
23
29
|
const task = bench.tasks[0]
|
|
24
30
|
const hz = task.result.throughput.mean // ops/sec
|
package/benchmark/bench.js
CHANGED
|
@@ -300,6 +300,92 @@ const benchmarks = [
|
|
|
300
300
|
},
|
|
301
301
|
input: { firstName: 'Max', lastName: 'Power', age: 22 }
|
|
302
302
|
},
|
|
303
|
+
{
|
|
304
|
+
name: 'oneOf external refs compile and first stringify',
|
|
305
|
+
compile: true,
|
|
306
|
+
schema: {
|
|
307
|
+
title: 'External refs oneOf schema',
|
|
308
|
+
type: 'object',
|
|
309
|
+
properties: {
|
|
310
|
+
content: {
|
|
311
|
+
oneOf: [
|
|
312
|
+
{ $ref: 'BenchmarkFoo#' },
|
|
313
|
+
{ $ref: 'BenchmarkBar#' },
|
|
314
|
+
{ $ref: 'BenchmarkBaz#' }
|
|
315
|
+
]
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
},
|
|
319
|
+
options: {
|
|
320
|
+
schema: {
|
|
321
|
+
BenchmarkFoo: {
|
|
322
|
+
type: 'object',
|
|
323
|
+
properties: {
|
|
324
|
+
kind: { const: 'foo' },
|
|
325
|
+
title: { type: 'string' },
|
|
326
|
+
count: { type: 'integer' },
|
|
327
|
+
flags: {
|
|
328
|
+
type: 'array',
|
|
329
|
+
items: { type: 'string' }
|
|
330
|
+
}
|
|
331
|
+
},
|
|
332
|
+
required: ['kind', 'title', 'count', 'flags'],
|
|
333
|
+
additionalProperties: false
|
|
334
|
+
},
|
|
335
|
+
BenchmarkBar: {
|
|
336
|
+
type: 'object',
|
|
337
|
+
properties: {
|
|
338
|
+
kind: { const: 'bar' },
|
|
339
|
+
name: { type: 'string' },
|
|
340
|
+
value: { type: 'number' },
|
|
341
|
+
meta: {
|
|
342
|
+
type: 'object',
|
|
343
|
+
properties: {
|
|
344
|
+
active: { type: 'boolean' },
|
|
345
|
+
tags: {
|
|
346
|
+
type: 'array',
|
|
347
|
+
items: { type: 'string' }
|
|
348
|
+
}
|
|
349
|
+
},
|
|
350
|
+
required: ['active', 'tags'],
|
|
351
|
+
additionalProperties: false
|
|
352
|
+
}
|
|
353
|
+
},
|
|
354
|
+
required: ['kind', 'name', 'value', 'meta'],
|
|
355
|
+
additionalProperties: false
|
|
356
|
+
},
|
|
357
|
+
BenchmarkBaz: {
|
|
358
|
+
type: 'object',
|
|
359
|
+
properties: {
|
|
360
|
+
kind: { const: 'baz' },
|
|
361
|
+
id: { type: 'string' },
|
|
362
|
+
nested: {
|
|
363
|
+
type: 'object',
|
|
364
|
+
properties: {
|
|
365
|
+
score: { type: 'number' },
|
|
366
|
+
note: { type: 'string' }
|
|
367
|
+
},
|
|
368
|
+
required: ['score', 'note'],
|
|
369
|
+
additionalProperties: false
|
|
370
|
+
}
|
|
371
|
+
},
|
|
372
|
+
required: ['kind', 'id', 'nested'],
|
|
373
|
+
additionalProperties: false
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
},
|
|
377
|
+
input: {
|
|
378
|
+
content: {
|
|
379
|
+
kind: 'bar',
|
|
380
|
+
name: 'benchmark',
|
|
381
|
+
value: 42.5,
|
|
382
|
+
meta: {
|
|
383
|
+
active: true,
|
|
384
|
+
tags: ['one', 'two', 'three']
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
},
|
|
303
389
|
{
|
|
304
390
|
name: 'object with const string property',
|
|
305
391
|
schema: {
|