fast-json-stringify 6.4.0 → 7.0.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.
@@ -2,12 +2,52 @@ version: 2
2
2
  updates:
3
3
  - package-ecosystem: "github-actions"
4
4
  directory: "/"
5
+ commit-message:
6
+ # Prefix all commit messages with "chore: "
7
+ prefix: "chore"
5
8
  schedule:
6
- interval: "monthly"
9
+ interval: "weekly"
10
+ cooldown:
11
+ default-days: 7
12
+ allow:
13
+ - dependency-name: "*"
14
+ update-types:
15
+ - "version-update:semver-major"
7
16
  open-pull-requests-limit: 10
8
17
 
9
18
  - package-ecosystem: "npm"
10
19
  directory: "/"
20
+ commit-message:
21
+ # Prefix all commit messages with "chore: "
22
+ prefix: "chore"
11
23
  schedule:
12
- interval: "monthly"
24
+ interval: "weekly"
25
+ cooldown:
26
+ default-days: 7
27
+ versioning-strategy: "increase-if-necessary"
28
+ allow:
29
+ - dependency-name: "*"
30
+ update-types:
31
+ - "version-update:semver-major"
32
+ ignore:
33
+ # TODO: remove ignore until neostandard support ESLint 10
34
+ - dependency-name: "eslint"
35
+ - dependency-name: "neostandard"
36
+ - dependency-name: "@stylistic/*"
13
37
  open-pull-requests-limit: 10
38
+ groups:
39
+ # Production dependencies with breaking changes
40
+ dependencies:
41
+ dependency-type: "production"
42
+ # ESLint related dependencies
43
+ dev-dependencies-eslint:
44
+ patterns:
45
+ - "eslint"
46
+ - "neostandard"
47
+ - "@stylistic/*"
48
+ # TypeScript related dependencies
49
+ dev-dependencies-typescript:
50
+ patterns:
51
+ - "@types/*"
52
+ - "tstyche"
53
+ - "typescript"
package/README.md CHANGED
@@ -11,7 +11,7 @@ Its performance advantage shrinks as your payload grows.
11
11
 
12
12
  ### How it works
13
13
 
14
- fast-json-stringify requires a [JSON Schema Draft 7](https://json-schema.org/specification-links.html#draft-7) input to generate a fast `stringify` function.
14
+ fast-json-stringify requires a [JSON Schema Draft 7](https://json-schema.org/specification-links#draft-7) input to generate a fast `stringify` function.
15
15
 
16
16
  ##### Benchmarks
17
17
 
@@ -137,7 +137,7 @@ const stringify = fastJson(mySchema, {
137
137
  <a name="fastJsonStringify"></a>
138
138
  ### fastJsonStringify(schema)
139
139
 
140
- Build a `stringify()` function based on [jsonschema draft 7 spec](https://json-schema.org/specification-links.html#draft-7).
140
+ Build a `stringify()` function based on [jsonschema draft 7 spec](https://json-schema.org/specification-links#draft-7).
141
141
 
142
142
  Supported types:
143
143
 
@@ -160,7 +160,7 @@ And nested ones, too.
160
160
  | `RegExp` | `string` |
161
161
  | `BigInt` | `integer` via `toString` |
162
162
 
163
- [JSON Schema built-in formats](https://json-schema.org/understanding-json-schema/reference/string.html#built-in-formats) for dates are supported and will be serialized as:
163
+ [JSON Schema built-in formats](https://json-schema.org/draft/2020-12/draft-bhutton-json-schema-validation-00#rfc.section.7.3.1) for dates are supported and will be serialized as:
164
164
 
165
165
  | Format | Serialized format example |
166
166
  | ----------- | -------------------------- |
@@ -436,7 +436,7 @@ console.log(stringify({
436
436
  <a name="ref"></a>
437
437
  #### Reuse - $ref
438
438
  If you want to reuse a definition of a value, you can use the property `$ref`.
439
- The value of `$ref` must be a string in [JSON Pointer](https://tools.ietf.org/html/rfc6901) format.
439
+ The value of `$ref` must be a string in [JSON Pointer](https://datatracker.ietf.org/doc/html/rfc6901) format.
440
440
  Example:
441
441
  ```javascript
442
442
  const schema = {
@@ -561,7 +561,7 @@ const stringify = fastJson(schema, { rounding: 'ceil' })
561
561
  <a name="nullable"></a>
562
562
  #### Nullable
563
563
 
564
- According to the [Open API 3.0 specification](https://swagger.io/docs/specification/data-models/data-types/#null), a value that can be null must be declared `nullable`.
564
+ According to the [Open API 3.0 specification](https://swagger.io/docs/specification/v3_0/data-models/data-types/#null), a value that can be null must be declared `nullable`.
565
565
 
566
566
  ##### Nullable object
567
567
  ```javascript
package/index.js CHANGED
@@ -10,8 +10,6 @@ const Location = require('./lib/location')
10
10
  const validate = require('./lib/schema-validator')
11
11
  const mergeSchemas = require('./lib/merge-schemas')
12
12
 
13
- const SINGLE_TICK = /'/g
14
-
15
13
  let largeArraySize = 2e4
16
14
  let largeArrayMechanism = 'default'
17
15
 
@@ -222,10 +220,6 @@ function build (schema, options) {
222
220
  }
223
221
  }
224
222
 
225
- if (options.debugMode) {
226
- options.mode = 'debug'
227
- }
228
-
229
223
  if (options.mode === 'debug') {
230
224
  return {
231
225
  validator,
@@ -386,10 +380,9 @@ function buildInnerObject (context, location, objVar) {
386
380
  const localUid = context.uid++
387
381
  let addComma = ''
388
382
 
389
- if (requiredProperties.length > 0) {
390
- // If we have required properties, we know that at least one property will be serialized.
391
- // We can avoid the runtime check for the comma.
392
-
383
+ // propertiesKeys is sorted required-first; the guard checks [0] is required because
384
+ // otherwise additionalProperties/patternProperties would emit a stray `{ ,"k":v }`.
385
+ if (propertiesKeys.length > 0 && requiredProperties.includes(propertiesKeys[0])) {
393
386
  // The first property is required, so we don't need a comma.
394
387
  // For the subsequent properties, we can blindly add a comma.
395
388
 
@@ -1078,7 +1071,7 @@ function buildConstSerializer (location, input) {
1078
1071
  `
1079
1072
  }
1080
1073
 
1081
- code += `json += '${JSON.stringify(schema.const).replace(SINGLE_TICK, "\\'")}'`
1074
+ code += `json += ${JSON.stringify(JSON.stringify(schema.const))}`
1082
1075
 
1083
1076
  if (hasNullType) {
1084
1077
  code += `
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fast-json-stringify",
3
- "version": "6.4.0",
3
+ "version": "7.0.1",
4
4
  "description": "Stringify your JSON at max speed",
5
5
  "main": "index.js",
6
6
  "type": "commonjs",
@@ -30,7 +30,7 @@
30
30
  "contributors": [
31
31
  {
32
32
  "name": "Tomas Della Vedova",
33
- "url": "http://delved.org"
33
+ "url": "https://delvedor.dev"
34
34
  },
35
35
  {
36
36
  "name": "Aras Abbasi",
@@ -81,7 +81,7 @@
81
81
  "@fastify/merge-json-schemas": "^0.2.0",
82
82
  "ajv": "^8.12.0",
83
83
  "ajv-formats": "^3.0.1",
84
- "fast-uri": "^3.0.0",
84
+ "fast-uri": "^4.0.0",
85
85
  "json-schema-ref-resolver": "^3.0.0",
86
86
  "rfdc": "^1.2.0"
87
87
  },
@@ -330,3 +330,50 @@ test('function and symbol references are not serialized as undefined', (t) => {
330
330
  const obj = { str: 'x', test: 'test', meth: () => 'x', sym: Symbol('x') }
331
331
  t.assert.equal(stringify(obj), '{"str":"x","test":"test"}')
332
332
  })
333
+
334
+ test('required + additionalProperties without declared properties produces valid JSON', (t) => {
335
+ // Regression: when `required` is non-empty but `properties` is absent
336
+ // (e.g. a record-style schema with only `additionalProperties`), the
337
+ // serializer used to emit a stray leading comma:
338
+ // {"obj":{,"a":1,"b":2}}
339
+ // because the "skip first comma" optimization assumed a declared
340
+ // property would anchor it. With no `properties`, the additionalProperties
341
+ // branch would write the separator before its first entry.
342
+ t.plan(2)
343
+ const stringify = build({
344
+ type: 'object',
345
+ properties: {
346
+ obj: {
347
+ type: 'object',
348
+ propertyNames: { type: 'string', enum: ['a', 'b'] },
349
+ additionalProperties: { type: 'number' },
350
+ required: ['a', 'b']
351
+ }
352
+ }
353
+ })
354
+
355
+ const out = stringify({ obj: { a: 1, b: 2 } })
356
+ t.assert.equal(out, '{"obj":{"a":1,"b":2}}')
357
+ t.assert.deepStrictEqual(JSON.parse(out), { obj: { a: 1, b: 2 } })
358
+ })
359
+
360
+ test('required key not in properties + additionalProperties produces valid JSON', (t) => {
361
+ // Regression: declared `properties` is non-empty but `required` only lists
362
+ // keys that are not in `properties`. After sorting, propertiesKeys[0] is
363
+ // not required, so the "first declared property anchors the comma" premise
364
+ // does not hold. The serializer used to emit '{,"str":"x"}' for input
365
+ // missing the (non-required) declared `num`.
366
+ t.plan(2)
367
+ const stringify = build({
368
+ type: 'object',
369
+ properties: {
370
+ num: { type: 'number' }
371
+ },
372
+ additionalProperties: true,
373
+ required: ['str']
374
+ })
375
+
376
+ const out = stringify({ str: 'x' })
377
+ t.assert.equal(out, '{"str":"x"}')
378
+ t.assert.deepStrictEqual(JSON.parse(out), { str: 'x' })
379
+ })
@@ -24,6 +24,26 @@ test('schema with const string', (t) => {
24
24
  t.assert.ok(validate(JSON.parse(output)), 'valid schema')
25
25
  })
26
26
 
27
+ test('schema with const string containing characters that need escaping', (t) => {
28
+ const values = ['back\\slash', 'tab\there', 'quote"here', 'new\nline', "tick's"]
29
+ t.plan(values.length * 2)
30
+
31
+ for (const value of values) {
32
+ const schema = {
33
+ type: 'object',
34
+ properties: {
35
+ foo: { const: value }
36
+ }
37
+ }
38
+
39
+ const stringify = build(schema)
40
+ const output = stringify({ foo: value })
41
+
42
+ t.assert.equal(output, JSON.stringify({ foo: value }))
43
+ t.assert.deepStrictEqual(JSON.parse(output), { foo: value })
44
+ }
45
+ })
46
+
27
47
  test('schema with const string and different input', (t) => {
28
48
  t.plan(2)
29
49
 
@@ -22,7 +22,7 @@ function build (opts) {
22
22
 
23
23
  test('activate debug mode', t => {
24
24
  t.plan(5)
25
- const debugMode = build({ debugMode: true })
25
+ const debugMode = build({ mode: 'debug' })
26
26
 
27
27
  t.assert.ok(typeof debugMode === 'object')
28
28
  t.assert.ok(debugMode.ajv instanceof Ajv)
@@ -31,21 +31,9 @@ test('activate debug mode', t => {
31
31
  t.assert.ok(typeof debugMode.code === 'string')
32
32
  })
33
33
 
34
- test('activate debug mode truthy', t => {
35
- t.plan(5)
36
-
37
- const debugMode = build({ debugMode: 'yes' })
38
-
39
- t.assert.ok(typeof debugMode === 'object')
40
- t.assert.ok(typeof debugMode.code === 'string')
41
- t.assert.ok(debugMode.ajv instanceof Ajv)
42
- t.assert.ok(debugMode.validator instanceof Validator)
43
- t.assert.ok(debugMode.serializer instanceof Serializer)
44
- })
45
-
46
34
  test('to string auto-consistent', t => {
47
35
  t.plan(6)
48
- const debugMode = build({ debugMode: 1 })
36
+ const debugMode = build({ mode: 'debug' })
49
37
 
50
38
  t.assert.ok(typeof debugMode === 'object')
51
39
  t.assert.ok(typeof debugMode.code === 'string')
@@ -73,7 +61,7 @@ test('to string auto-consistent with ajv', t => {
73
61
  }]
74
62
  }
75
63
  }
76
- }, { debugMode: 1 })
64
+ }, { mode: 'debug' })
77
65
 
78
66
  t.assert.ok(typeof debugMode === 'object')
79
67
  t.assert.ok(typeof debugMode.code === 'string')
@@ -102,7 +90,7 @@ test('to string auto-consistent with ajv-formats', t => {
102
90
  }]
103
91
  }
104
92
  }
105
- }, { debugMode: 1 })
93
+ }, { mode: 'debug' })
106
94
 
107
95
  t.assert.ok(typeof debugMode === 'object')
108
96
 
@@ -115,7 +103,7 @@ test('to string auto-consistent with ajv-formats', t => {
115
103
  test('debug should restore the same serializer instance', t => {
116
104
  t.plan(1)
117
105
 
118
- const debugMode = fjs({ type: 'integer' }, { debugMode: 1, rounding: 'ceil' })
106
+ const debugMode = fjs({ type: 'integer' }, { mode: 'debug', rounding: 'ceil' })
119
107
  const compiled = fjs.restore(debugMode)
120
108
  t.assert.equal(compiled(3.95), 4)
121
109
  })
@@ -166,3 +166,27 @@ test('patternProperties - fail on invalid regex, handled by ajv', (t) => {
166
166
  }
167
167
  }), new Error('schema is invalid: data/patternProperties must match format "regex"'))
168
168
  })
169
+
170
+ test('required key not in properties + patternProperties produces valid JSON', (t) => {
171
+ // Regression: symmetric case to the additionalProperties variant. `required`
172
+ // names a key not in `properties`, so after sorting propertiesKeys[0] is not
173
+ // required and the "first declared property anchors the comma" premise does
174
+ // not hold. The patternProperties branch shares the same code path, so the
175
+ // same stray-leading-comma bug would surface here without the tightened
176
+ // guard in `index.js`.
177
+ t.plan(2)
178
+ const stringify = build({
179
+ type: 'object',
180
+ properties: {
181
+ num: { type: 'number' }
182
+ },
183
+ patternProperties: {
184
+ '^s_': { type: 'string' }
185
+ },
186
+ required: ['s_x']
187
+ })
188
+
189
+ const out = stringify({ s_x: 'x' })
190
+ t.assert.equal(out, '{"s_x":"x"}')
191
+ t.assert.deepStrictEqual(JSON.parse(out), { s_x: 'x' })
192
+ })
package/types/index.d.ts CHANGED
@@ -162,16 +162,10 @@ declare namespace build {
162
162
  * @default 'trunc'
163
163
  */
164
164
  rounding?: 'ceil' | 'floor' | 'round' | 'trunc'
165
- /**
166
- * @deprecated
167
- * Enable debug mode. Please use `mode: "debug"` instead
168
- */
169
- debugMode?: boolean
170
165
  /**
171
166
  * Running mode of fast-json-stringify
172
167
  */
173
168
  mode?: 'debug' | 'standalone'
174
-
175
169
  /**
176
170
  * Large arrays are defined as arrays containing, by default, `20000`
177
171
  * elements or more. That value can be adjusted via the option parameter
@@ -180,7 +174,6 @@ declare namespace build {
180
174
  * @default 20000
181
175
  */
182
176
  largeArraySize?: number | string | BigInt
183
-
184
177
  /**
185
178
  * Specify the function on how large Arrays should be stringified.
186
179
  *
@@ -200,10 +193,6 @@ interface DebugOption extends build.Options {
200
193
  mode: 'debug'
201
194
  }
202
195
 
203
- interface DeprecateDebugOption extends build.Options {
204
- debugMode: true
205
- }
206
-
207
196
  interface StandaloneOption extends build.Options {
208
197
  mode: 'standalone'
209
198
  }
@@ -217,7 +206,6 @@ type IntegerCoercible = number | BigInt
217
206
  * @param options The options to use (optional)
218
207
  */
219
208
  declare function build (schema: build.AnySchema, options: DebugOption): { code: string, ajv: Ajv }
220
- declare function build (schema: build.AnySchema, options: DeprecateDebugOption): { code: string, ajv: Ajv }
221
209
  declare function build (schema: build.AnySchema, options: StandaloneOption): string
222
210
  declare function build (schema: build.AnySchema, options?: build.Options): <TDoc = any>(doc: TDoc) => any
223
211
  declare function build (schema: build.StringSchema, options?: build.Options): <TDoc extends StringCoercible = StringCoercible>(doc: TDoc) => string