fast-json-stringify 7.0.1 → 7.1.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/lib/serializer.js CHANGED
@@ -3,6 +3,43 @@
3
3
  // eslint-disable-next-line
4
4
  const STR_ESCAPE = /[\u0000-\u001f\u0022\u005c\ud800-\udfff]/
5
5
 
6
+ function asString (str) {
7
+ const len = str.length
8
+ if (len === 0) {
9
+ return '""'
10
+ } else if (len < 42) {
11
+ // magically escape strings for json
12
+ // relying on their charCodeAt
13
+ // everything below 32 needs JSON.stringify()
14
+ // every string that contain surrogate needs JSON.stringify()
15
+ // 34 and 92 happens all the time, so we
16
+ // have a fast case for them
17
+ let result = ''
18
+ let last = -1
19
+ let point = 255
20
+ for (let i = 0; i < len; i++) {
21
+ point = str.charCodeAt(i)
22
+ if (
23
+ point === 0x22 || // '"'
24
+ point === 0x5c // '\'
25
+ ) {
26
+ last === -1 && (last = 0)
27
+ result += str.slice(last, i) + '\\'
28
+ last = i
29
+ } else if (point < 32 || (point >= 0xD800 && point <= 0xDFFF)) {
30
+ // The current character is non-printable characters or a surrogate.
31
+ return JSON.stringify(str)
32
+ }
33
+ }
34
+ return (last === -1 && ('"' + str + '"')) || ('"' + result + str.slice(last) + '"')
35
+ } else if (len < 5000 && STR_ESCAPE.test(str) === false) {
36
+ // Only use the regular expression for shorter input. The overhead is otherwise too much.
37
+ return '"' + str + '"'
38
+ } else {
39
+ return JSON.stringify(str)
40
+ }
41
+ }
42
+
6
43
  module.exports = class Serializer {
7
44
  constructor (options) {
8
45
  switch (options && options.rounding) {
@@ -63,7 +100,7 @@ module.exports = class Serializer {
63
100
  return '"' + date.toISOString() + '"'
64
101
  }
65
102
  if (typeof date === 'string') {
66
- return '"' + date + '"'
103
+ return asString(date)
67
104
  }
68
105
  throw new Error(`The value "${date}" cannot be converted to a date-time.`)
69
106
  }
@@ -74,7 +111,7 @@ module.exports = class Serializer {
74
111
  return '"' + new Date(date.getTime() - (date.getTimezoneOffset() * 60000)).toISOString().slice(0, 10) + '"'
75
112
  }
76
113
  if (typeof date === 'string') {
77
- return '"' + date + '"'
114
+ return asString(date)
78
115
  }
79
116
  throw new Error(`The value "${date}" cannot be converted to a date.`)
80
117
  }
@@ -85,46 +122,13 @@ module.exports = class Serializer {
85
122
  return '"' + new Date(date.getTime() - (date.getTimezoneOffset() * 60000)).toISOString().slice(11, 19) + '"'
86
123
  }
87
124
  if (typeof date === 'string') {
88
- return '"' + date + '"'
125
+ return asString(date)
89
126
  }
90
127
  throw new Error(`The value "${date}" cannot be converted to a time.`)
91
128
  }
92
129
 
93
130
  asString (str) {
94
- const len = str.length
95
- if (len === 0) {
96
- return '""'
97
- } else if (len < 42) {
98
- // magically escape strings for json
99
- // relying on their charCodeAt
100
- // everything below 32 needs JSON.stringify()
101
- // every string that contain surrogate needs JSON.stringify()
102
- // 34 and 92 happens all the time, so we
103
- // have a fast case for them
104
- let result = ''
105
- let last = -1
106
- let point = 255
107
- for (let i = 0; i < len; i++) {
108
- point = str.charCodeAt(i)
109
- if (
110
- point === 0x22 || // '"'
111
- point === 0x5c // '\'
112
- ) {
113
- last === -1 && (last = 0)
114
- result += str.slice(last, i) + '\\'
115
- last = i
116
- } else if (point < 32 || (point >= 0xD800 && point <= 0xDFFF)) {
117
- // The current character is non-printable characters or a surrogate.
118
- return JSON.stringify(str)
119
- }
120
- }
121
- return (last === -1 && ('"' + str + '"')) || ('"' + result + str.slice(last) + '"')
122
- } else if (len < 5000 && STR_ESCAPE.test(str) === false) {
123
- // Only use the regular expression for shorter input. The overhead is otherwise too much.
124
- return '"' + str + '"'
125
- } else {
126
- return JSON.stringify(str)
127
- }
131
+ return asString(str)
128
132
  }
129
133
 
130
134
  asUnsafeString (str) {
package/lib/standalone.js CHANGED
@@ -3,9 +3,28 @@
3
3
  function buildStandaloneCode (contextFunc, context, serializer, validator) {
4
4
  let ajvDependencyCode = ''
5
5
  if (context.validatorSchemasIds.size > 0) {
6
- ajvDependencyCode += 'const Validator = require(\'fast-json-stringify/lib/validator\')\n'
7
- ajvDependencyCode += `const validatorState = ${JSON.stringify(validator.getState())}\n`
8
- ajvDependencyCode += 'const validator = Validator.restoreFromState(validatorState)\n'
6
+ if (context.options.inlineValidators) {
7
+ const standaloneCode = require('ajv/dist/standalone').default
8
+ const schemaRefs = Object.fromEntries(
9
+ [...context.validatorSchemaRefs].map(schemaRef => [schemaRef, schemaRef])
10
+ )
11
+
12
+ ajvDependencyCode += `const validator = (() => {
13
+ const validators = {}
14
+ const exports = validators
15
+ ${standaloneCode(validator.ajv, schemaRefs)}
16
+ return {
17
+ validate (schemaRef, data) {
18
+ const validate = validators[schemaRef]
19
+ return validate(data)
20
+ }
21
+ }
22
+ })()\n`
23
+ } else {
24
+ ajvDependencyCode += 'const Validator = require(\'fast-json-stringify/lib/validator\')\n'
25
+ ajvDependencyCode += `const validatorState = ${JSON.stringify(validator.getState())}\n`
26
+ ajvDependencyCode += 'const validator = Validator.restoreFromState(validatorState)\n'
27
+ }
9
28
  } else {
10
29
  ajvDependencyCode += 'const validator = null\n'
11
30
  }
package/lib/validator.js CHANGED
@@ -1,14 +1,20 @@
1
1
  'use strict'
2
2
 
3
3
  const Ajv = require('ajv')
4
+ const { _ } = Ajv
4
5
  const fastUri = require('fast-uri')
5
6
  const ajvFormats = require('ajv-formats')
6
7
  const clone = require('rfdc')({ proto: true })
7
8
 
8
9
  class Validator {
9
- constructor (ajvOptions) {
10
+ constructor (ajvOptions, inlineValidators) {
11
+ const codeOptions = inlineValidators
12
+ ? { code: { ...ajvOptions?.code, source: true, esm: false } }
13
+ : {}
14
+
10
15
  this.ajv = new Ajv({
11
16
  ...ajvOptions,
17
+ ...codeOptions,
12
18
  strictSchema: false,
13
19
  validateSchema: false,
14
20
  allowUnionTypes: true,
@@ -21,8 +27,9 @@ class Validator {
21
27
  keyword: 'fjs_type',
22
28
  type: 'object',
23
29
  errors: false,
24
- validate: (_type, data) => {
25
- return data && typeof data.toJSON === 'function'
30
+ code (context) {
31
+ const { data } = context
32
+ context.fail(_`!${data} || typeof ${data}.toJSON !== "function"`)
26
33
  }
27
34
  })
28
35
 
@@ -47,6 +54,18 @@ class Validator {
47
54
  }
48
55
  }
49
56
 
57
+ // Ajv compiles a schema lazily, on the first `validate` call for a given ref.
58
+ // That cost would otherwise be paid at serialization time, on the first
59
+ // document that exercises the union/if-then-else branch. Eagerly resolving
60
+ // every ref the generated code can validate against moves it to build time.
61
+ // It must run after every `addSchema` call: compiling resolves $refs, so
62
+ // doing it earlier breaks on forward references.
63
+ compileSchemas (schemaRefs) {
64
+ for (const schemaRef of schemaRefs) {
65
+ this.ajv.getSchema(schemaRef)
66
+ }
67
+ }
68
+
50
69
  validate (schemaRef, data) {
51
70
  return this.ajv.validate(schemaRef, data)
52
71
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fast-json-stringify",
3
- "version": "7.0.1",
3
+ "version": "7.1.0",
4
4
  "description": "Stringify your JSON at max speed",
5
5
  "main": "index.js",
6
6
  "type": "commonjs",
@@ -13,7 +13,7 @@
13
13
  "lint": "eslint",
14
14
  "lint:fix": "eslint --fix",
15
15
  "test:typescript": "tstyche",
16
- "test:unit": "c8 node --test",
16
+ "test:unit": "c8 --100 --all --include=index.js --include=lib/**/*.js --exclude=lib/schema-validator.js node --test",
17
17
  "test": "npm run test:unit && npm run test:typescript"
18
18
  },
19
19
  "repository": {
@@ -62,15 +62,14 @@
62
62
  }
63
63
  ],
64
64
  "devDependencies": {
65
- "c8": "^11.0.0",
65
+ "c8": "^12.0.0",
66
66
  "cli-select": "^1.1.2",
67
67
  "compile-json-stringify": "^0.1.2",
68
68
  "eslint": "^9.39.2",
69
69
  "fast-json-stringify": ".",
70
- "is-my-json-valid": "^2.20.6",
71
70
  "json-accelerator": "^0.1.7",
72
71
  "neostandard": "^0.13.0",
73
- "simple-git": "^3.30.0",
72
+ "simple-git": "^4.0.2",
74
73
  "tinybench": "^6.0.0",
75
74
  "tstyche": "^7.1.0",
76
75
  "typebox": "^1.0.81",
@@ -377,3 +377,43 @@ test('required key not in properties + additionalProperties produces valid JSON'
377
377
  t.assert.equal(out, '{"str":"x"}')
378
378
  t.assert.deepStrictEqual(JSON.parse(out), { str: 'x' })
379
379
  })
380
+
381
+ test('additionalProperties set to false ignores properties not matched by patternProperties', (t) => {
382
+ // Regression: `additionalProperties: false` is documented to drop every
383
+ // property that is not listed in `properties` or matched by
384
+ // `patternProperties`. Combined with `patternProperties`, the generated
385
+ // code used to fall through to the additionalProperties branch with a
386
+ // boolean `false` schema, which serialized unmatched properties with
387
+ // `JSON.stringify(value)` and leaked them into the output:
388
+ // {"nickname":"nick","matchnum":3,"leaked":"secret"}
389
+ t.plan(2)
390
+ const stringify = build({
391
+ type: 'object',
392
+ properties: {
393
+ nickname: { type: 'string' }
394
+ },
395
+ patternProperties: {
396
+ num: { type: 'number' }
397
+ },
398
+ additionalProperties: false
399
+ })
400
+
401
+ const out = stringify({ nickname: 'nick', matchnum: 3, leaked: 'secret' })
402
+ t.assert.equal(out, '{"nickname":"nick","matchnum":3}')
403
+ t.assert.deepStrictEqual(JSON.parse(out), { nickname: 'nick', matchnum: 3 })
404
+ })
405
+
406
+ test('additionalProperties set to false without declared properties ignores unmatched properties', (t) => {
407
+ t.plan(2)
408
+ const stringify = build({
409
+ type: 'object',
410
+ patternProperties: {
411
+ '^str': { type: 'string' }
412
+ },
413
+ additionalProperties: false
414
+ })
415
+
416
+ const out = stringify({ str1: 'a', leaked: 'secret' })
417
+ t.assert.equal(out, '{"str1":"a"}')
418
+ t.assert.deepStrictEqual(JSON.parse(out), { str1: 'a' })
419
+ })
@@ -387,6 +387,48 @@ test('anyOf and $ref - multiple external $ref', (t) => {
387
387
  t.assert.equal(output, '{"obj":{"prop":{"prop2":"test"}}}')
388
388
  })
389
389
 
390
+ test('anyOf with registered $ref branches validates directly against the ref target', (t) => {
391
+ t.plan(3)
392
+
393
+ const schema = {
394
+ title: 'anyOf with registered $ref branches',
395
+ type: 'object',
396
+ properties: {
397
+ content: {
398
+ anyOf: [
399
+ { $ref: 'Foo#' },
400
+ { $ref: 'Bar#/definitions/Baz' }
401
+ ]
402
+ }
403
+ }
404
+ }
405
+ const externalSchema = {
406
+ Foo: {
407
+ type: 'object',
408
+ properties: { a: { type: 'string' } },
409
+ required: ['a'],
410
+ additionalProperties: false
411
+ },
412
+ Bar: {
413
+ definitions: {
414
+ Baz: {
415
+ type: 'object',
416
+ properties: { b: { type: 'string' } },
417
+ required: ['b'],
418
+ additionalProperties: false
419
+ }
420
+ }
421
+ }
422
+ }
423
+
424
+ const code = build(schema, { mode: 'standalone', schema: externalSchema })
425
+ t.assert.ok(code.includes('validator.validate("Foo#"'), 'validates directly against the bare registered id')
426
+ t.assert.ok(code.includes('validator.validate("Bar#/definitions/Baz"'), 'validates directly against the registered sub-path ref')
427
+
428
+ const stringify = build(schema, { schema: externalSchema })
429
+ t.assert.equal(stringify({ content: { b: 'yo' } }), '{"content":{"b":"yo"}}')
430
+ })
431
+
390
432
  test('anyOf looks for all of the array items', (t) => {
391
433
  t.plan(1)
392
434
 
@@ -1,7 +1,7 @@
1
1
  'use strict'
2
2
 
3
3
  const { test } = require('node:test')
4
- const validator = require('is-my-json-valid')
4
+ const validator = require('./utils/validator')
5
5
  const build = require('..')
6
6
  const Ajv = require('ajv')
7
7
 
@@ -1,7 +1,7 @@
1
1
  'use strict'
2
2
 
3
3
  const { test } = require('node:test')
4
- const validator = require('is-my-json-valid')
4
+ const validator = require('./utils/validator')
5
5
  const build = require('..')
6
6
 
7
7
  function buildTest (schema, toStringify) {
@@ -0,0 +1,158 @@
1
+ 'use strict'
2
+
3
+ const { test } = require('node:test')
4
+
5
+ const OriginalLocation = require('../lib/location')
6
+
7
+ const buildPath = require.resolve('..')
8
+ const locationPath = require.resolve('../lib/location')
9
+
10
+ function loadBuildWithLocation (Location) {
11
+ require.cache[locationPath].exports = Location
12
+ delete require.cache[buildPath]
13
+
14
+ try {
15
+ return require(buildPath)
16
+ } finally {
17
+ require.cache[locationPath].exports = OriginalLocation
18
+ delete require.cache[buildPath]
19
+ }
20
+ }
21
+
22
+ class LocationWithoutSchemaId {
23
+ constructor (schema, _schemaId, jsonPointer = '#') {
24
+ this.schema = schema
25
+ this.schemaId = ''
26
+ this.jsonPointer = _schemaId ? _schemaId + jsonPointer : jsonPointer
27
+ }
28
+
29
+ getPropertyLocation (propertyName) {
30
+ return new LocationWithoutSchemaId(
31
+ this.schema[propertyName],
32
+ '',
33
+ this.jsonPointer + '/' + propertyName
34
+ )
35
+ }
36
+
37
+ getSchemaRef () {
38
+ return this.schemaId + this.jsonPointer
39
+ }
40
+ }
41
+
42
+ class LocationWithoutJsonPointer {
43
+ constructor (schema, schemaId) {
44
+ this.schema = schema
45
+ this.schemaId = schemaId
46
+ this.jsonPointer = ''
47
+ }
48
+
49
+ getPropertyLocation (propertyName) {
50
+ return new LocationWithoutJsonPointer(this.schema[propertyName], this.schemaId)
51
+ }
52
+
53
+ getSchemaRef () {
54
+ return this.schemaId
55
+ }
56
+ }
57
+
58
+ class LocationWithoutSchemaRef extends OriginalLocation {
59
+ getSchemaRef () {}
60
+ }
61
+
62
+ test('inline object generation without schema IDs', t => {
63
+ t.plan(3)
64
+
65
+ const build = loadBuildWithLocation(LocationWithoutSchemaId)
66
+ const stringify = build({
67
+ type: 'object',
68
+ properties: {
69
+ value: { type: 'string' }
70
+ }
71
+ })
72
+ const stringifyNullable = build({ type: 'object', nullable: true })
73
+
74
+ t.assert.equal(stringify({ value: 'test' }), '{"value":"test"}')
75
+ t.assert.equal(stringify(null), '{}')
76
+ t.assert.equal(stringifyNullable(null), 'null')
77
+ })
78
+
79
+ test('inline array generation without schema IDs', t => {
80
+ t.plan(6)
81
+
82
+ const build = loadBuildWithLocation(LocationWithoutSchemaId)
83
+ const stringify = build({ type: 'array', items: { type: 'string' } }, { largeArrayMechanism: 'default' })
84
+ const stringifyNullable = build({ type: 'array', nullable: true })
85
+ const stringifyTuple = build({
86
+ type: 'array',
87
+ items: [{ $ref: 'item' }, { type: 'number' }],
88
+ additionalItems: true
89
+ }, {
90
+ schema: {
91
+ item: { type: 'string' }
92
+ }
93
+ })
94
+ const stringifyFixedTuple = build({
95
+ type: 'array',
96
+ items: [{ type: 'string' }],
97
+ additionalItems: false
98
+ })
99
+ const stringifyLargeArray = build({ type: 'array', items: { type: 'number' } }, {
100
+ largeArrayMechanism: 'json-stringify',
101
+ largeArraySize: 1
102
+ })
103
+
104
+ t.assert.equal(stringify(['one', 'two']), '["one","two"]')
105
+ t.assert.equal(stringifyNullable(null), 'null')
106
+ t.assert.equal(stringifyTuple(['one', 2, true]), '["one",2,true]')
107
+ t.assert.throws(() => stringifyFixedTuple(['one', 'two']), /Item at 1/)
108
+ t.assert.equal(stringifyLargeArray([1, 2]), '[1,2]')
109
+ t.assert.throws(() => stringify('not-an-array'), /does not match schema definition/)
110
+ })
111
+
112
+ test('code generation reference fallbacks', t => {
113
+ t.plan(3)
114
+
115
+ const buildWithoutPointer = loadBuildWithLocation(LocationWithoutJsonPointer)
116
+ const stringifyObject = buildWithoutPointer({ type: 'object' })
117
+ const stringifyArray = buildWithoutPointer({ type: 'array' })
118
+
119
+ const buildWithoutRef = loadBuildWithLocation(LocationWithoutSchemaRef)
120
+ const stringifyWithoutRef = buildWithoutRef({ type: 'object' })
121
+
122
+ t.assert.equal(stringifyObject({}), '{}')
123
+ t.assert.equal(stringifyArray([]), '[]')
124
+ t.assert.equal(stringifyWithoutRef({}), '{}')
125
+ })
126
+
127
+ test('required-property fallback tolerates unexpected property ordering', t => {
128
+ t.plan(2)
129
+
130
+ const originalSort = Array.prototype.sort
131
+ // Deliberately fault-inject unexpected ordering to exercise the defensive branch.
132
+ // eslint-disable-next-line no-extend-native
133
+ Array.prototype.sort = function (compareFn) {
134
+ const result = originalSort.call(this, compareFn)
135
+ if (this.length === 2 && this.includes('optional') && this.includes('required')) {
136
+ result.reverse()
137
+ }
138
+ return result
139
+ }
140
+
141
+ try {
142
+ const build = loadBuildWithLocation(OriginalLocation)
143
+ const stringify = build({
144
+ type: 'object',
145
+ properties: {
146
+ optional: { type: 'string' },
147
+ required: { type: 'string' }
148
+ },
149
+ required: ['required']
150
+ })
151
+
152
+ t.assert.equal(stringify({ optional: 'one', required: 'two' }), '{"optional":"one","required":"two"}')
153
+ t.assert.throws(() => stringify({ optional: 'one' }), /"required" is required/)
154
+ } finally {
155
+ // eslint-disable-next-line no-extend-native
156
+ Array.prototype.sort = originalSort
157
+ }
158
+ })
@@ -0,0 +1,110 @@
1
+ 'use strict'
2
+
3
+ const { test } = require('node:test')
4
+ const fjs = require('..')
5
+
6
+ const schema = {
7
+ title: 'compile validators',
8
+ type: 'object',
9
+ properties: {
10
+ union: {
11
+ anyOf: [
12
+ { type: 'string' },
13
+ { type: 'number' }
14
+ ]
15
+ },
16
+ choice: {
17
+ oneOf: [
18
+ { type: 'object', properties: { a: { type: 'string' } }, required: ['a'] },
19
+ { type: 'object', properties: { b: { type: 'number' } }, required: ['b'] }
20
+ ]
21
+ },
22
+ conditional: {
23
+ if: { type: 'string' },
24
+ then: { type: 'string' },
25
+ else: { type: 'number' }
26
+ }
27
+ }
28
+ }
29
+
30
+ // The refs the generated code validates against, straight out of the emitted
31
+ // source, so the assertions cannot drift from what fjs actually calls.
32
+ function getValidatorRefs (code) {
33
+ return [...code.matchAll(/validator\.validate\("([^"]+)"/g)].map(match => match[1])
34
+ }
35
+
36
+ // Ajv stores a compiled schema as a SchemaEnv with a `validate` function.
37
+ // `getSchema()` is not used here because it compiles on demand, which is the
38
+ // very thing under test.
39
+ function isCompiled (ajv, ref) {
40
+ const schemaEnv = ajv.refs[ref] || ajv.schemas[ref]
41
+ return schemaEnv !== undefined && typeof schemaEnv.validate === 'function'
42
+ }
43
+
44
+ test('validators are compiled lazily by default', t => {
45
+ t.plan(2)
46
+
47
+ const { code, ajv } = fjs(schema, { mode: 'debug' })
48
+ const refs = getValidatorRefs(code)
49
+
50
+ t.assert.ok(refs.length > 0)
51
+ t.assert.deepStrictEqual(refs.filter(ref => isCompiled(ajv, ref)), [])
52
+ })
53
+
54
+ test('compileValidators compiles every validator used by the generated code', t => {
55
+ t.plan(2)
56
+
57
+ const { code, ajv } = fjs(schema, { mode: 'debug', compileValidators: true })
58
+ const refs = getValidatorRefs(code)
59
+
60
+ t.assert.ok(refs.length > 0)
61
+ t.assert.deepStrictEqual(refs.filter(ref => !isCompiled(ajv, ref)), [])
62
+ })
63
+
64
+ test('compileValidators does not change the serialization output', t => {
65
+ t.plan(2)
66
+
67
+ const input = {
68
+ union: 'foo',
69
+ choice: { b: 42 },
70
+ conditional: 'bar'
71
+ }
72
+ const expected = JSON.stringify({ union: 'foo', choice: { b: 42 }, conditional: 'bar' })
73
+
74
+ t.assert.equal(fjs(schema)(input), expected)
75
+ t.assert.equal(fjs(schema, { compileValidators: true })(input), expected)
76
+ })
77
+
78
+ test('compileValidators works with external schemas resolved after registration', t => {
79
+ t.plan(3)
80
+
81
+ const externalSchema = {
82
+ branch: {
83
+ $id: 'branch',
84
+ definitions: {
85
+ str: { type: 'string' },
86
+ num: { type: 'number' }
87
+ },
88
+ type: 'object',
89
+ properties: {
90
+ value: {
91
+ anyOf: [
92
+ { $ref: 'branch#/definitions/str' },
93
+ { $ref: 'branch#/definitions/num' }
94
+ ]
95
+ }
96
+ }
97
+ }
98
+ }
99
+
100
+ const rootSchema = { $ref: 'branch#' }
101
+ const options = { schema: externalSchema, compileValidators: true }
102
+
103
+ const { code, ajv } = fjs(rootSchema, { ...options, mode: 'debug' })
104
+ const refs = getValidatorRefs(code)
105
+
106
+ t.assert.ok(refs.length > 0)
107
+ t.assert.deepStrictEqual(refs.filter(ref => !isCompiled(ajv, ref)), [])
108
+
109
+ t.assert.equal(fjs(rootSchema, options)({ value: 7 }), JSON.stringify({ value: 7 }))
110
+ })
@@ -1,7 +1,7 @@
1
1
  'use strict'
2
2
 
3
3
  const { test } = require('node:test')
4
- const validator = require('is-my-json-valid')
4
+ const validator = require('./utils/validator')
5
5
  const build = require('..')
6
6
 
7
7
  test('schema with const string', (t) => {
@@ -279,7 +279,9 @@ test('schema with const and null as type', (t) => {
279
279
  })
280
280
 
281
281
  t.assert.equal(output, '{"foo":null}')
282
- t.assert.ok(validate(JSON.parse(output)), 'valid schema')
282
+ // `const` applies regardless of `type` in JSON Schema, so ajv rejects null
283
+ // here even though the serializer lets it through because the type allows it.
284
+ t.assert.equal(validate(JSON.parse(output)), false)
283
285
 
284
286
  const output2 = stringify({ foo: 'baz' })
285
287
  t.assert.equal(output2, '{"foo":"baz"}')
@@ -287,7 +289,7 @@ test('schema with const and null as type', (t) => {
287
289
  })
288
290
 
289
291
  test('schema with const as nullable', (t) => {
290
- t.plan(4)
292
+ t.plan(2)
291
293
 
292
294
  const schema = {
293
295
  type: 'object',
@@ -296,20 +298,19 @@ test('schema with const as nullable', (t) => {
296
298
  }
297
299
  }
298
300
 
299
- const validate = validator(schema)
301
+ // ajv refuses to compile `nullable` without a `type`, so the output is not
302
+ // validated against the schema in this test.
300
303
  const stringify = build(schema)
301
304
  const output = stringify({
302
305
  foo: null
303
306
  })
304
307
 
305
308
  t.assert.equal(output, '{"foo":null}')
306
- t.assert.ok(validate(JSON.parse(output)), 'valid schema')
307
309
 
308
310
  const output2 = stringify({
309
311
  foo: 'baz'
310
312
  })
311
313
  t.assert.equal(output2, '{"foo":"baz"}')
312
- t.assert.ok(validate(JSON.parse(output2)), 'valid schema')
313
314
  })
314
315
 
315
316
  test('schema with const and invalid object', (t) => {