fast-json-stringify 5.2.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.
@@ -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
File without changes
File without changes
@@ -18,14 +18,6 @@ module.exports = class Serializer {
18
18
  }
19
19
  }
20
20
 
21
- asAny (i) {
22
- return JSON.stringify(i)
23
- }
24
-
25
- asNull () {
26
- return 'null'
27
- }
28
-
29
21
  asInteger (i) {
30
22
  if (typeof i === 'bigint') {
31
23
  return i.toString()
@@ -42,10 +34,6 @@ module.exports = class Serializer {
42
34
  }
43
35
  }
44
36
 
45
- asIntegerNullable (i) {
46
- return i === null ? 'null' : this.asInteger(i)
47
- }
48
-
49
37
  asNumber (i) {
50
38
  const num = Number(i)
51
39
  if (Number.isNaN(num)) {
@@ -57,18 +45,10 @@ module.exports = class Serializer {
57
45
  }
58
46
  }
59
47
 
60
- asNumberNullable (i) {
61
- return i === null ? 'null' : this.asNumber(i)
62
- }
63
-
64
48
  asBoolean (bool) {
65
49
  return bool && 'true' || 'false' // eslint-disable-line
66
50
  }
67
51
 
68
- asBooleanNullable (bool) {
69
- return bool === null ? 'null' : this.asBoolean(bool)
70
- }
71
-
72
52
  asDateTime (date) {
73
53
  if (date === null) return '""'
74
54
  if (date instanceof Date) {
@@ -80,10 +60,6 @@ module.exports = class Serializer {
80
60
  throw new Error(`The value "${date}" cannot be converted to a date-time.`)
81
61
  }
82
62
 
83
- asDateTimeNullable (date) {
84
- return date === null ? 'null' : this.asDateTime(date)
85
- }
86
-
87
63
  asDate (date) {
88
64
  if (date === null) return '""'
89
65
  if (date instanceof Date) {
@@ -95,10 +71,6 @@ module.exports = class Serializer {
95
71
  throw new Error(`The value "${date}" cannot be converted to a date.`)
96
72
  }
97
73
 
98
- asDateNullable (date) {
99
- return date === null ? 'null' : this.asDate(date)
100
- }
101
-
102
74
  asTime (date) {
103
75
  if (date === null) return '""'
104
76
  if (date instanceof Date) {
@@ -110,10 +82,6 @@ module.exports = class Serializer {
110
82
  throw new Error(`The value "${date}" cannot be converted to a time.`)
111
83
  }
112
84
 
113
- asTimeNullable (date) {
114
- return date === null ? 'null' : this.asTime(date)
115
- }
116
-
117
85
  asString (str) {
118
86
  const quotes = '"'
119
87
  if (str instanceof Date) {
@@ -133,10 +101,6 @@ module.exports = class Serializer {
133
101
  }
134
102
  }
135
103
 
136
- asStringNullable (str) {
137
- return str === null ? 'null' : this.asString(str)
138
- }
139
-
140
104
  // magically escape strings for json
141
105
  // relying on their charCodeAt
142
106
  // everything below 32 needs JSON.stringify()
@@ -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
- if (shouldUseAjv) {
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
File without changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fast-json-stringify",
3
- "version": "5.2.0",
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
- "test:typescript": "tsc --project ./test/types/tsconfig.json",
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:lint && npm run test:unit && npm run test:typescript"
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,6 +45,7 @@
45
45
  "simple-git": "^3.7.1",
46
46
  "standard": "^17.0.0",
47
47
  "tap": "^16.0.1",
48
+ "tsd": "^0.24.1",
48
49
  "typescript": "^4.0.2",
49
50
  "webpack": "^5.40.0"
50
51
  },
@@ -61,5 +62,8 @@
61
62
  "schema-validator.js"
62
63
  ]
63
64
  },
64
- "runkitExampleFilename": "example.js"
65
+ "runkitExampleFilename": "./examples/example.js",
66
+ "tsd": {
67
+ "directory": "test/types"
68
+ }
65
69
  }
@@ -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
+ })
@@ -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
+ })
@@ -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)
@@ -327,6 +345,31 @@ test('object array with anyOf and symbol', (t) => {
327
345
  t.equal(value, '[{"name":"name-0","option":"Foo"},{"name":"name-1","option":"Bar"}]')
328
346
  })
329
347
 
348
+ test('different arrays with same item schemas', (t) => {
349
+ t.plan(1)
350
+
351
+ const schema = {
352
+ type: 'object',
353
+ properties: {
354
+ array1: {
355
+ type: 'array',
356
+ items: [{ type: 'string' }],
357
+ additionalItems: false
358
+ },
359
+ array2: {
360
+ type: 'array',
361
+ items: { $ref: '#/properties/array1/items' },
362
+ additionalItems: true
363
+ }
364
+ }
365
+ }
366
+
367
+ const stringify = build(schema)
368
+ const data = { array1: ['bar'], array2: ['foo', 'bar'] }
369
+
370
+ t.equal(stringify(data), '{"array1":["bar"],"array2":["foo","bar"]}')
371
+ })
372
+
330
373
  const largeArray = new Array(2e4).fill({ a: 'test', b: 1 })
331
374
  buildTest({
332
375
  title: 'large array with default mechanism',
package/test/date.test.js CHANGED
@@ -525,3 +525,22 @@ test('nullable date', (t) => {
525
525
 
526
526
  t.same(result, `"${DateTime.fromJSDate(data).toISODate()}"`)
527
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
+ })
@@ -4,7 +4,8 @@ const test = require('tap').test
4
4
  const fjs = require('..')
5
5
 
6
6
  const Ajv = require('ajv').default
7
- const Validator = require('../validator')
7
+ const Validator = require('../lib/validator')
8
+ const Serializer = require('../lib/serializer')
8
9
 
9
10
  function build (opts) {
10
11
  return fjs({
@@ -20,17 +21,18 @@ function build (opts) {
20
21
  }
21
22
 
22
23
  test('activate debug mode', t => {
23
- t.plan(4)
24
+ t.plan(5)
24
25
  const debugMode = build({ debugMode: true })
25
26
 
26
27
  t.type(debugMode, 'object')
27
28
  t.ok(debugMode.ajv instanceof Ajv)
28
29
  t.ok(debugMode.validator instanceof Validator)
30
+ t.ok(debugMode.serializer instanceof Serializer)
29
31
  t.type(debugMode.code, 'string')
30
32
  })
31
33
 
32
34
  test('activate debug mode truthy', t => {
33
- t.plan(4)
35
+ t.plan(5)
34
36
 
35
37
  const debugMode = build({ debugMode: 'yes' })
36
38
 
@@ -38,15 +40,17 @@ test('activate debug mode truthy', t => {
38
40
  t.type(debugMode.code, 'string')
39
41
  t.ok(debugMode.ajv instanceof Ajv)
40
42
  t.ok(debugMode.validator instanceof Validator)
43
+ t.ok(debugMode.serializer instanceof Serializer)
41
44
  })
42
45
 
43
46
  test('to string auto-consistent', t => {
44
- t.plan(5)
47
+ t.plan(6)
45
48
  const debugMode = build({ debugMode: 1 })
46
49
 
47
50
  t.type(debugMode, 'object')
48
51
  t.type(debugMode.code, 'string')
49
52
  t.ok(debugMode.ajv instanceof Ajv)
53
+ t.ok(debugMode.serializer instanceof Serializer)
50
54
  t.ok(debugMode.validator instanceof Validator)
51
55
 
52
56
  const compiled = fjs.restore(debugMode)
@@ -55,7 +59,7 @@ test('to string auto-consistent', t => {
55
59
  })
56
60
 
57
61
  test('to string auto-consistent with ajv', t => {
58
- t.plan(5)
62
+ t.plan(6)
59
63
 
60
64
  const debugMode = fjs({
61
65
  title: 'object with multiple types field',
@@ -75,6 +79,7 @@ test('to string auto-consistent with ajv', t => {
75
79
  t.type(debugMode.code, 'string')
76
80
  t.ok(debugMode.ajv instanceof Ajv)
77
81
  t.ok(debugMode.validator instanceof Validator)
82
+ t.ok(debugMode.serializer instanceof Serializer)
78
83
 
79
84
  const compiled = fjs.restore(debugMode)
80
85
  const tobe = JSON.stringify({ str: 'Foo' })
@@ -106,3 +111,11 @@ test('to string auto-consistent with ajv-formats', t => {
106
111
  t.same(compiled({ str: 'foo@bar.com' }), tobe)
107
112
  t.throws(() => compiled({ str: 'foo' }))
108
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
+ })
@@ -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
+ })
@@ -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"));