fast-json-stringify 5.0.6 → 5.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/benchmark.yml +12 -12
- package/.github/workflows/ci.yml +2 -0
- package/benchmark/bench.js +54 -0
- package/{build-schema-validator.js → build/build-schema-validator.js} +1 -1
- package/{example.js → examples/example.js} +1 -1
- package/{server.js → examples/server.js} +1 -1
- package/index.d.ts +9 -8
- package/index.js +232 -270
- package/lib/ref-resolver.js +84 -0
- package/{schema-validator.js → lib/schema-validator.js} +0 -0
- package/{serializer.js → lib/serializer.js} +19 -46
- package/{standalone.js → lib/standalone.js} +8 -8
- package/lib/validator.js +75 -0
- package/package.json +11 -6
- package/test/array.test.js +25 -0
- package/test/basic.test.js +13 -0
- package/test/const.test.js +235 -8
- package/test/date.test.js +119 -4
- package/test/debug-mode.test.js +23 -4
- package/test/nullable.test.js +97 -0
- package/test/ref.test.js +28 -0
- package/test/types/schema-inference.test-d.ts +48 -0
- package/test/typesArray.test.js +29 -0
- package/ajv.js +0 -37
package/test/date.test.js
CHANGED
|
@@ -308,7 +308,7 @@ test('serializing null value', t => {
|
|
|
308
308
|
})
|
|
309
309
|
|
|
310
310
|
t.test('type::array', t => {
|
|
311
|
-
t.plan(
|
|
311
|
+
t.plan(6)
|
|
312
312
|
|
|
313
313
|
t.test('format::date-time', t => {
|
|
314
314
|
t.plan(2)
|
|
@@ -348,13 +348,13 @@ test('serializing null value', t => {
|
|
|
348
348
|
t.notOk(validate(JSON.parse(output)), 'an empty string is not a date format')
|
|
349
349
|
})
|
|
350
350
|
|
|
351
|
-
t.test('format::
|
|
351
|
+
t.test('format::date', t => {
|
|
352
352
|
t.plan(2)
|
|
353
353
|
|
|
354
354
|
const prop = {
|
|
355
355
|
updatedAt: {
|
|
356
356
|
type: ['string'],
|
|
357
|
-
format: '
|
|
357
|
+
format: 'date'
|
|
358
358
|
}
|
|
359
359
|
}
|
|
360
360
|
|
|
@@ -364,7 +364,66 @@ test('serializing null value', t => {
|
|
|
364
364
|
} = serialize(createSchema(prop), input)
|
|
365
365
|
|
|
366
366
|
t.equal(output, '{"updatedAt":""}')
|
|
367
|
-
t.notOk(validate(JSON.parse(output)), 'an empty string is not a
|
|
367
|
+
t.notOk(validate(JSON.parse(output)), 'an empty string is not a date format')
|
|
368
|
+
})
|
|
369
|
+
|
|
370
|
+
t.test('format::time, Date object', t => {
|
|
371
|
+
t.plan(1)
|
|
372
|
+
|
|
373
|
+
const schema = {
|
|
374
|
+
oneOf: [
|
|
375
|
+
{
|
|
376
|
+
type: 'object',
|
|
377
|
+
properties: {
|
|
378
|
+
updatedAt: {
|
|
379
|
+
type: ['string', 'number'],
|
|
380
|
+
format: 'time'
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
]
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
const date = new Date()
|
|
388
|
+
const input = { updatedAt: date }
|
|
389
|
+
const { output } = serialize(schema, input)
|
|
390
|
+
|
|
391
|
+
t.equal(output, JSON.stringify({ updatedAt: DateTime.fromJSDate(date).toFormat('HH:mm:ss') }))
|
|
392
|
+
})
|
|
393
|
+
|
|
394
|
+
t.test('format::time, Date object', t => {
|
|
395
|
+
t.plan(1)
|
|
396
|
+
|
|
397
|
+
const schema = {
|
|
398
|
+
oneOf: [
|
|
399
|
+
{
|
|
400
|
+
type: ['string', 'number'],
|
|
401
|
+
format: 'time'
|
|
402
|
+
}
|
|
403
|
+
]
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
const date = new Date()
|
|
407
|
+
const { output } = serialize(schema, date)
|
|
408
|
+
|
|
409
|
+
t.equal(output, `"${DateTime.fromJSDate(date).toFormat('HH:mm:ss')}"`)
|
|
410
|
+
})
|
|
411
|
+
|
|
412
|
+
t.test('format::time, Date object', t => {
|
|
413
|
+
t.plan(1)
|
|
414
|
+
|
|
415
|
+
const schema = {
|
|
416
|
+
oneOf: [
|
|
417
|
+
{
|
|
418
|
+
type: ['string', 'number'],
|
|
419
|
+
format: 'time'
|
|
420
|
+
}
|
|
421
|
+
]
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
const { output } = serialize(schema, 42)
|
|
425
|
+
|
|
426
|
+
t.equal(output, JSON.stringify(42))
|
|
368
427
|
})
|
|
369
428
|
})
|
|
370
429
|
|
|
@@ -429,3 +488,59 @@ test('serializing null value', t => {
|
|
|
429
488
|
})
|
|
430
489
|
})
|
|
431
490
|
})
|
|
491
|
+
|
|
492
|
+
test('Validate Date object as string type', (t) => {
|
|
493
|
+
t.plan(1)
|
|
494
|
+
|
|
495
|
+
const schema = {
|
|
496
|
+
oneOf: [
|
|
497
|
+
{ type: 'string' }
|
|
498
|
+
]
|
|
499
|
+
}
|
|
500
|
+
const toStringify = new Date()
|
|
501
|
+
|
|
502
|
+
const stringify = build(schema)
|
|
503
|
+
const output = stringify(toStringify)
|
|
504
|
+
|
|
505
|
+
t.equal(output, JSON.stringify(toStringify))
|
|
506
|
+
})
|
|
507
|
+
|
|
508
|
+
test('nullable date', (t) => {
|
|
509
|
+
t.plan(1)
|
|
510
|
+
|
|
511
|
+
const schema = {
|
|
512
|
+
anyOf: [
|
|
513
|
+
{
|
|
514
|
+
format: 'date',
|
|
515
|
+
type: 'string',
|
|
516
|
+
nullable: true
|
|
517
|
+
}
|
|
518
|
+
]
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
const stringify = build(schema)
|
|
522
|
+
|
|
523
|
+
const data = new Date()
|
|
524
|
+
const result = stringify(data)
|
|
525
|
+
|
|
526
|
+
t.same(result, `"${DateTime.fromJSDate(data).toISODate()}"`)
|
|
527
|
+
})
|
|
528
|
+
|
|
529
|
+
test('non-date format should not affect data serialization (issue #491)', (t) => {
|
|
530
|
+
t.plan(1)
|
|
531
|
+
|
|
532
|
+
const schema = {
|
|
533
|
+
type: 'object',
|
|
534
|
+
properties: {
|
|
535
|
+
hello: {
|
|
536
|
+
type: 'string',
|
|
537
|
+
format: 'int64',
|
|
538
|
+
pattern: '^[0-9]*$'
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
const stringify = build(schema)
|
|
544
|
+
const data = { hello: 123n }
|
|
545
|
+
t.equal(stringify(data), '{"hello":"123"}')
|
|
546
|
+
})
|
package/test/debug-mode.test.js
CHANGED
|
@@ -2,7 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
const test = require('tap').test
|
|
4
4
|
const fjs = require('..')
|
|
5
|
+
|
|
5
6
|
const Ajv = require('ajv').default
|
|
7
|
+
const Validator = require('../lib/validator')
|
|
8
|
+
const Serializer = require('../lib/serializer')
|
|
6
9
|
|
|
7
10
|
function build (opts) {
|
|
8
11
|
return fjs({
|
|
@@ -18,31 +21,37 @@ function build (opts) {
|
|
|
18
21
|
}
|
|
19
22
|
|
|
20
23
|
test('activate debug mode', t => {
|
|
21
|
-
t.plan(
|
|
24
|
+
t.plan(5)
|
|
22
25
|
const debugMode = build({ debugMode: true })
|
|
23
26
|
|
|
24
27
|
t.type(debugMode, 'object')
|
|
25
28
|
t.ok(debugMode.ajv instanceof Ajv)
|
|
29
|
+
t.ok(debugMode.validator instanceof Validator)
|
|
30
|
+
t.ok(debugMode.serializer instanceof Serializer)
|
|
26
31
|
t.type(debugMode.code, 'string')
|
|
27
32
|
})
|
|
28
33
|
|
|
29
34
|
test('activate debug mode truthy', t => {
|
|
30
|
-
t.plan(
|
|
35
|
+
t.plan(5)
|
|
31
36
|
|
|
32
37
|
const debugMode = build({ debugMode: 'yes' })
|
|
33
38
|
|
|
34
39
|
t.type(debugMode, 'object')
|
|
35
40
|
t.type(debugMode.code, 'string')
|
|
36
41
|
t.ok(debugMode.ajv instanceof Ajv)
|
|
42
|
+
t.ok(debugMode.validator instanceof Validator)
|
|
43
|
+
t.ok(debugMode.serializer instanceof Serializer)
|
|
37
44
|
})
|
|
38
45
|
|
|
39
46
|
test('to string auto-consistent', t => {
|
|
40
|
-
t.plan(
|
|
47
|
+
t.plan(6)
|
|
41
48
|
const debugMode = build({ debugMode: 1 })
|
|
42
49
|
|
|
43
50
|
t.type(debugMode, 'object')
|
|
44
51
|
t.type(debugMode.code, 'string')
|
|
45
52
|
t.ok(debugMode.ajv instanceof Ajv)
|
|
53
|
+
t.ok(debugMode.serializer instanceof Serializer)
|
|
54
|
+
t.ok(debugMode.validator instanceof Validator)
|
|
46
55
|
|
|
47
56
|
const compiled = fjs.restore(debugMode)
|
|
48
57
|
const tobe = JSON.stringify({ firstName: 'Foo' })
|
|
@@ -50,7 +59,7 @@ test('to string auto-consistent', t => {
|
|
|
50
59
|
})
|
|
51
60
|
|
|
52
61
|
test('to string auto-consistent with ajv', t => {
|
|
53
|
-
t.plan(
|
|
62
|
+
t.plan(6)
|
|
54
63
|
|
|
55
64
|
const debugMode = fjs({
|
|
56
65
|
title: 'object with multiple types field',
|
|
@@ -69,6 +78,8 @@ test('to string auto-consistent with ajv', t => {
|
|
|
69
78
|
t.type(debugMode, 'object')
|
|
70
79
|
t.type(debugMode.code, 'string')
|
|
71
80
|
t.ok(debugMode.ajv instanceof Ajv)
|
|
81
|
+
t.ok(debugMode.validator instanceof Validator)
|
|
82
|
+
t.ok(debugMode.serializer instanceof Serializer)
|
|
72
83
|
|
|
73
84
|
const compiled = fjs.restore(debugMode)
|
|
74
85
|
const tobe = JSON.stringify({ str: 'Foo' })
|
|
@@ -100,3 +111,11 @@ test('to string auto-consistent with ajv-formats', t => {
|
|
|
100
111
|
t.same(compiled({ str: 'foo@bar.com' }), tobe)
|
|
101
112
|
t.throws(() => compiled({ str: 'foo' }))
|
|
102
113
|
})
|
|
114
|
+
|
|
115
|
+
test('debug should restore the same serializer instance', t => {
|
|
116
|
+
t.plan(1)
|
|
117
|
+
|
|
118
|
+
const debugMode = fjs({ type: 'integer' }, { debugMode: 1, rounding: 'ceil' })
|
|
119
|
+
const compiled = fjs.restore(debugMode)
|
|
120
|
+
t.same(compiled(3.95), 4)
|
|
121
|
+
})
|
package/test/nullable.test.js
CHANGED
|
@@ -444,3 +444,100 @@ test('large array of nullable booleans with default mechanism', (t) => {
|
|
|
444
444
|
t.same(result, JSON.stringify(data))
|
|
445
445
|
t.same(JSON.parse(result), data)
|
|
446
446
|
})
|
|
447
|
+
|
|
448
|
+
test('nullable type in the schema', (t) => {
|
|
449
|
+
t.plan(2)
|
|
450
|
+
|
|
451
|
+
const schema = {
|
|
452
|
+
type: ['object', 'null'],
|
|
453
|
+
properties: {
|
|
454
|
+
foo: {
|
|
455
|
+
type: 'string'
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
const stringify = build(schema)
|
|
461
|
+
|
|
462
|
+
const data = { foo: 'bar' }
|
|
463
|
+
|
|
464
|
+
t.same(stringify(data), JSON.stringify(data))
|
|
465
|
+
t.same(stringify(null), JSON.stringify(null))
|
|
466
|
+
})
|
|
467
|
+
|
|
468
|
+
test('throw an error if the value doesn\'t match the type', (t) => {
|
|
469
|
+
t.plan(2)
|
|
470
|
+
|
|
471
|
+
const schema = {
|
|
472
|
+
type: 'object',
|
|
473
|
+
additionalProperties: false,
|
|
474
|
+
required: ['data'],
|
|
475
|
+
properties: {
|
|
476
|
+
data: {
|
|
477
|
+
type: 'array',
|
|
478
|
+
minItems: 1,
|
|
479
|
+
items: {
|
|
480
|
+
oneOf: [
|
|
481
|
+
{
|
|
482
|
+
type: 'string'
|
|
483
|
+
},
|
|
484
|
+
{
|
|
485
|
+
type: 'number'
|
|
486
|
+
}
|
|
487
|
+
]
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
const stringify = build(schema)
|
|
494
|
+
|
|
495
|
+
const validData = { data: [1, 'testing'] }
|
|
496
|
+
t.equal(stringify(validData), JSON.stringify(validData))
|
|
497
|
+
|
|
498
|
+
const invalidData = { data: [false, 'testing'] }
|
|
499
|
+
t.throws(() => stringify(invalidData))
|
|
500
|
+
})
|
|
501
|
+
|
|
502
|
+
test('nullable value in oneOf', (t) => {
|
|
503
|
+
t.plan(1)
|
|
504
|
+
|
|
505
|
+
const schema = {
|
|
506
|
+
type: 'object',
|
|
507
|
+
properties: {
|
|
508
|
+
data: {
|
|
509
|
+
oneOf: [
|
|
510
|
+
{
|
|
511
|
+
type: 'array',
|
|
512
|
+
items: {
|
|
513
|
+
type: 'object',
|
|
514
|
+
properties: {
|
|
515
|
+
id: { type: 'integer', minimum: 1 }
|
|
516
|
+
},
|
|
517
|
+
additionalProperties: false,
|
|
518
|
+
required: ['id']
|
|
519
|
+
}
|
|
520
|
+
},
|
|
521
|
+
{
|
|
522
|
+
type: 'array',
|
|
523
|
+
items: {
|
|
524
|
+
type: 'object',
|
|
525
|
+
properties: {
|
|
526
|
+
job: { type: 'string', nullable: true }
|
|
527
|
+
},
|
|
528
|
+
additionalProperties: false,
|
|
529
|
+
required: ['job']
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
]
|
|
533
|
+
}
|
|
534
|
+
},
|
|
535
|
+
required: ['data'],
|
|
536
|
+
additionalProperties: false
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
const stringify = build(schema)
|
|
540
|
+
|
|
541
|
+
const data = { data: [{ job: null }] }
|
|
542
|
+
t.equal(stringify(data), JSON.stringify(data))
|
|
543
|
+
})
|
package/test/ref.test.js
CHANGED
|
@@ -1953,3 +1953,31 @@ test('should resolve absolute $refs', (t) => {
|
|
|
1953
1953
|
|
|
1954
1954
|
t.equal(output, JSON.stringify(object))
|
|
1955
1955
|
})
|
|
1956
|
+
|
|
1957
|
+
test('nested schema should overwrite anchor scope', (t) => {
|
|
1958
|
+
t.plan(2)
|
|
1959
|
+
|
|
1960
|
+
const externalSchema = {
|
|
1961
|
+
root: {
|
|
1962
|
+
$id: 'root',
|
|
1963
|
+
definitions: {
|
|
1964
|
+
subschema: {
|
|
1965
|
+
$id: 'subschema',
|
|
1966
|
+
definitions: {
|
|
1967
|
+
anchorSchema: {
|
|
1968
|
+
$id: '#anchor',
|
|
1969
|
+
type: 'string'
|
|
1970
|
+
}
|
|
1971
|
+
}
|
|
1972
|
+
}
|
|
1973
|
+
}
|
|
1974
|
+
}
|
|
1975
|
+
}
|
|
1976
|
+
|
|
1977
|
+
const data = 'test'
|
|
1978
|
+
const stringify = build({ $ref: 'subschema#anchor' }, { schema: externalSchema })
|
|
1979
|
+
const output = stringify(data)
|
|
1980
|
+
|
|
1981
|
+
t.equal(output, JSON.stringify(data))
|
|
1982
|
+
t.throws(() => build({ $ref: 'root#anchor' }, { schema: externalSchema }))
|
|
1983
|
+
})
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { expectError } from "tsd";
|
|
2
|
+
import build from "../..";
|
|
3
|
+
|
|
4
|
+
// With inference
|
|
5
|
+
interface Schema {
|
|
6
|
+
id: string;
|
|
7
|
+
a?: number;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const stringify3 = build({
|
|
11
|
+
type: "object",
|
|
12
|
+
properties: { a: { type: "string" } },
|
|
13
|
+
});
|
|
14
|
+
stringify3<Schema>({ id: "123" });
|
|
15
|
+
stringify3<Schema>({ a: 123, id: "123" });
|
|
16
|
+
expectError(stringify3<Schema>({ anotherOne: "bar" }));
|
|
17
|
+
expectError(stringify3<Schema>({ a: "bar" }));
|
|
18
|
+
|
|
19
|
+
// Without inference
|
|
20
|
+
const stringify4 = build({
|
|
21
|
+
type: "object",
|
|
22
|
+
properties: { a: { type: "string" } },
|
|
23
|
+
});
|
|
24
|
+
stringify4({ id: "123" });
|
|
25
|
+
stringify4({ a: 123, id: "123" });
|
|
26
|
+
stringify4({ anotherOne: "bar" });
|
|
27
|
+
stringify4({ a: "bar" });
|
|
28
|
+
|
|
29
|
+
// Without inference - string type
|
|
30
|
+
const stringify5 = build({
|
|
31
|
+
type: "string",
|
|
32
|
+
});
|
|
33
|
+
stringify5("foo");
|
|
34
|
+
expectError(stringify5({ id: "123" }));
|
|
35
|
+
|
|
36
|
+
// Without inference - null type
|
|
37
|
+
const stringify6 = build({
|
|
38
|
+
type: "null",
|
|
39
|
+
});
|
|
40
|
+
stringify6(null);
|
|
41
|
+
expectError(stringify6("a string"));
|
|
42
|
+
|
|
43
|
+
// Without inference - boolean type
|
|
44
|
+
const stringify7 = build({
|
|
45
|
+
type: "boolean",
|
|
46
|
+
});
|
|
47
|
+
stringify7(true);
|
|
48
|
+
expectError(stringify7("a string"));
|
package/test/typesArray.test.js
CHANGED
|
@@ -438,6 +438,35 @@ test('object that is simultaneously a string and a json switched', (t) => {
|
|
|
438
438
|
t.equal(valueObj, '{"simultaneously":{"foo":"hello"}}')
|
|
439
439
|
})
|
|
440
440
|
|
|
441
|
+
test('class instance that is simultaneously a string and a json', (t) => {
|
|
442
|
+
t.plan(2)
|
|
443
|
+
|
|
444
|
+
const schema = {
|
|
445
|
+
type: 'object',
|
|
446
|
+
properties: {
|
|
447
|
+
simultaneously: {
|
|
448
|
+
type: ['string', 'object'],
|
|
449
|
+
properties: {
|
|
450
|
+
foo: { type: 'string' }
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
class Test {
|
|
457
|
+
toString () { return 'hello' }
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
const likeObjectId = new Test()
|
|
461
|
+
|
|
462
|
+
const stringify = build(schema)
|
|
463
|
+
const valueStr = stringify({ simultaneously: likeObjectId })
|
|
464
|
+
t.equal(valueStr, '{"simultaneously":"hello"}')
|
|
465
|
+
|
|
466
|
+
const valueObj = stringify({ simultaneously: { foo: likeObjectId } })
|
|
467
|
+
t.equal(valueObj, '{"simultaneously":{"foo":"hello"}}')
|
|
468
|
+
})
|
|
469
|
+
|
|
441
470
|
test('should throw an error when type is array and object is null', (t) => {
|
|
442
471
|
t.plan(1)
|
|
443
472
|
const schema = {
|
package/ajv.js
DELETED
|
@@ -1,37 +0,0 @@
|
|
|
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, validateSchema: 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
|
-
}
|