fast-json-stringify 5.4.0 → 5.5.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/README.md CHANGED
@@ -18,31 +18,37 @@ fast-json-stringify requires a [JSON Schema Draft 7](https://json-schema.org/spe
18
18
  ##### Benchmarks
19
19
 
20
20
  - Machine: `EX41S-SSD, Intel Core i7, 4Ghz, 64GB RAM, 4C/8T, SSD`.
21
- - Node.js `v16.9.1`
21
+ - Node.js `v18.12.1`
22
22
 
23
23
  ```
24
- FJS creation x 8,443 ops/sec ±1.01% (90 runs sampled)
25
- CJS creation x 183,219 ops/sec ±0.13% (96 runs sampled)
26
- AJV Serialize creation x 83,541,848 ops/sec ±0.24% (98 runs sampled)
27
- JSON.stringify array x 5,363 ops/sec ±0.11% (100 runs sampled)
28
- fast-json-stringify array x 6,747 ops/sec ±0.13% (98 runs sampled)
29
- compile-json-stringify array x 7,121 ops/sec ±0.42% (98 runs sampled)
30
- AJV Serialize array x 7,533 ops/sec ±0.13% (98 runs sampled)
31
- JSON.stringify long string x 16,461 ops/sec ±0.12% (98 runs sampled)
32
- fast-json-stringify long string x 16,443 ops/sec ±0.37% (99 runs sampled)
33
- compile-json-stringify long string x 16,458 ops/sec ±0.09% (98 runs sampled)
34
- AJV Serialize long string x 21,433 ops/sec ±0.08% (95 runs sampled)
35
- JSON.stringify short string x 12,035,664 ops/sec ±0.62% (96 runs sampled)
36
- fast-json-stringify short string x 38,281,060 ops/sec ±0.24% (98 runs sampled)
37
- compile-json-stringify short string x 32,388,037 ops/sec ±0.27% (97 runs sampled)
38
- AJV Serialize short string x 32,288,612 ops/sec ±0.32% (95 runs sampled)
39
- JSON.stringify obj x 3,068,185 ops/sec ±0.16% (98 runs sampled)
40
- fast-json-stringify obj x 10,082,694 ops/sec ±0.10% (97 runs sampled)
41
- compile-json-stringify obj x 17,037,963 ops/sec ±1.17% (97 runs sampled)
42
- AJV Serialize obj x 9,660,041 ops/sec ±0.11% (97 runs sampled)
43
- JSON stringify date x 1,084,008 ops/sec ±0.16% (98 runs sampled)
44
- fast-json-stringify date format x 1,781,044 ops/sec ±0.48% (99 runs sampled)
45
- compile-json-stringify date format x 1,086,187 ops/sec ±0.16% (99 runs sampled)
24
+ FJS creation x 4,129 ops/sec ±0.82% (92 runs sampled)
25
+ CJS creation x 184,196 ops/sec ±0.12% (97 runs sampled)
26
+ AJV Serialize creation x 61,130,591 ops/sec ±0.40% (92 runs sampled)
27
+ JSON.stringify array x 5,057 ops/sec ±0.10% (100 runs sampled)
28
+ fast-json-stringify array default x 6,243 ops/sec ±0.14% (98 runs sampled)
29
+ fast-json-stringify array json-stringify x 6,261 ops/sec ±0.30% (99 runs sampled)
30
+ compile-json-stringify array x 6,842 ops/sec ±0.18% (96 runs sampled)
31
+ AJV Serialize array x 6,964 ops/sec ±0.11% (95 runs sampled)
32
+ JSON.stringify large array x 248 ops/sec ±0.07% (90 runs sampled)
33
+ fast-json-stringify large array default x 99.96 ops/sec ±0.22% (74 runs sampled)
34
+ fast-json-stringify large array json-stringify x 248 ops/sec ±0.07% (90 runs sampled)
35
+ compile-json-stringify large array x 317 ops/sec ±0.09% (89 runs sampled)
36
+ AJV Serialize large array x 111 ops/sec ±0.07% (33 runs sampled)
37
+ JSON.stringify long string x 16,002 ops/sec ±0.09% (98 runs sampled)
38
+ fast-json-stringify long string x 15,979 ops/sec ±0.09% (96 runs sampled)
39
+ compile-json-stringify long string x 15,952 ops/sec ±0.31% (97 runs sampled)
40
+ AJV Serialize long string x 21,416 ops/sec ±0.08% (98 runs sampled)
41
+ JSON.stringify short string x 12,944,272 ops/sec ±0.09% (96 runs sampled)
42
+ fast-json-stringify short string x 30,585,790 ops/sec ±0.27% (97 runs sampled)
43
+ compile-json-stringify short string x 30,656,406 ops/sec ±0.12% (96 runs sampled)
44
+ AJV Serialize short string x 30,406,785 ops/sec ±0.37% (96 runs sampled)
45
+ JSON.stringify obj x 3,153,043 ops/sec ±0.33% (99 runs sampled)
46
+ fast-json-stringify obj x 6,866,434 ops/sec ±0.11% (100 runs sampled)
47
+ compile-json-stringify obj x 15,886,723 ops/sec ±0.15% (98 runs sampled)
48
+ AJV Serialize obj x 8,969,043 ops/sec ±0.36% (97 runs sampled)
49
+ JSON stringify date x 1,126,547 ops/sec ±0.09% (97 runs sampled)
50
+ fast-json-stringify date format x 1,836,188 ops/sec ±0.12% (99 runs sampled)
51
+ compile-json-stringify date format x 1,125,735 ops/sec ±0.19% (98 runs sampled)
46
52
  ```
47
53
 
48
54
  #### Table of contents:
@@ -644,12 +650,16 @@ AJV Serialize large array x 58.76 ops/sec ±4.59% (60 runs sampled)
644
650
  Treat the schema definition as application code, it
645
651
  is not safe to use user-provided schemas.
646
652
 
647
- In order to achieve lowest cost/highest performance redaction `fast-json-stringify`
653
+ To achieve low cost and high performance redaction `fast-json-stringify`
648
654
  creates and compiles a function (using the `Function` constructor) on initialization.
649
655
  While the `schema` is currently validated for any developer errors,
650
656
  there is no guarantee that supplying user-generated schema could not
651
657
  expose your application to remote attacks.
652
658
 
659
+ Users are responsible for sending trusted data. `fast-json-stringify` guarantees that you will get
660
+ a valid output only if your input matches the schema or can be coerced to the schema. If your input
661
+ doesn't match the schema, you will get undefined behavior.
662
+
653
663
  <a name="debug"></a>
654
664
  ### Debug Mode
655
665
 
package/index.js CHANGED
@@ -56,11 +56,7 @@ function resolveRef (location, ref) {
56
56
  throw new Error(`Cannot find reference "${ref}"`)
57
57
  }
58
58
 
59
- if (location.isValidated) {
60
- validatorSchemasIds.add(schemaId)
61
- }
62
-
63
- const newLocation = new Location(schema, schemaId, jsonPointer, location.isValidated)
59
+ const newLocation = new Location(schema, schemaId, jsonPointer)
64
60
  if (schema.$ref !== undefined) {
65
61
  return resolveRef(newLocation, schema.$ref)
66
62
  }
@@ -137,6 +133,11 @@ function build (schema, options) {
137
133
  for (const schemaId of validatorSchemasIds) {
138
134
  const schema = refResolver.getSchema(schemaId)
139
135
  validator.addSchema(schema, schemaId)
136
+
137
+ const dependencies = refResolver.getSchemaDependencies(schemaId)
138
+ for (const [schemaId, schema] of Object.entries(dependencies)) {
139
+ validator.addSchema(schema, schemaId)
140
+ }
140
141
  }
141
142
 
142
143
  const dependenciesName = ['validator', 'serializer', contextFunctionCode]
@@ -454,7 +455,6 @@ function mergeAllOfSchema (location, schema, mergedSchema) {
454
455
  }
455
456
 
456
457
  function addIfThenElse (location, input) {
457
- location.isValidated = true
458
458
  validatorSchemasIds.add(location.getSchemaId())
459
459
 
460
460
  const schema = merge({}, location.schema)
@@ -840,7 +840,6 @@ function buildValue (location, input) {
840
840
  let code = ''
841
841
 
842
842
  if (type === undefined && (schema.anyOf || schema.oneOf)) {
843
- location.isValidated = true
844
843
  validatorSchemasIds.add(location.getSchemaId())
845
844
 
846
845
  const type = schema.anyOf ? 'anyOf' : 'oneOf'
@@ -889,6 +888,8 @@ function buildValue (location, input) {
889
888
  }
890
889
 
891
890
  module.exports = build
891
+ module.exports.default = build
892
+ module.exports.build = build
892
893
 
893
894
  module.exports.validLargeArrayMechanisms = validLargeArrayMechanisms
894
895
 
package/lib/location.js CHANGED
@@ -1,11 +1,10 @@
1
1
  'use strict'
2
2
 
3
3
  class Location {
4
- constructor (schema, schemaId, jsonPointer = '#', isValidated = false) {
4
+ constructor (schema, schemaId, jsonPointer = '#') {
5
5
  this.schema = schema
6
6
  this.schemaId = schemaId
7
7
  this.jsonPointer = jsonPointer
8
- this.isValidated = isValidated
9
8
  this.mergedSchemaId = null
10
9
  }
11
10
 
@@ -13,8 +12,7 @@ class Location {
13
12
  const propertyLocation = new Location(
14
13
  this.schema[propertyName],
15
14
  this.schemaId,
16
- this.jsonPointer + '/' + propertyName,
17
- this.isValidated
15
+ this.jsonPointer + '/' + propertyName
18
16
  )
19
17
 
20
18
  if (this.mergedSchemaId !== null) {
@@ -28,6 +28,18 @@ class RefResolver {
28
28
  return getDataByJSONPointer(schema.schema, jsonPointer)
29
29
  }
30
30
 
31
+ getSchemaDependencies (schemaId, dependencies = {}) {
32
+ const schema = this.schemas[schemaId]
33
+
34
+ for (const dependencySchemaId of schema.dependencies) {
35
+ if (dependencies[dependencySchemaId] !== undefined) continue
36
+ dependencies[dependencySchemaId] = this.getSchema(dependencySchemaId)
37
+ this.getSchemaDependencies(dependencySchemaId, dependencies)
38
+ }
39
+
40
+ return dependencies
41
+ }
42
+
31
43
  insertSchemaBySchemaId (schema, schemaId) {
32
44
  if (
33
45
  this.schemas[schemaId] !== undefined &&
@@ -35,7 +47,7 @@ class RefResolver {
35
47
  ) {
36
48
  throw new Error(`There is already another schema with id ${schemaId}`)
37
49
  }
38
- this.schemas[schemaId] = { schema, anchors: {} }
50
+ this.schemas[schemaId] = { schema, anchors: {}, dependencies: [] }
39
51
  }
40
52
 
41
53
  insertSchemaByAnchor (schema, schemaId, anchor) {
@@ -60,6 +72,14 @@ class RefResolver {
60
72
  }
61
73
  }
62
74
 
75
+ const ref = schema.$ref
76
+ if (ref !== undefined && typeof ref === 'string') {
77
+ if (ref.charAt(0) !== '#') {
78
+ const dependencySchemaId = ref.split('#')[0]
79
+ this.schemas[rootSchemaId].dependencies.push(dependencySchemaId)
80
+ }
81
+ }
82
+
63
83
  for (const key in schema) {
64
84
  if (typeof schema[key] === 'object' && schema[key] !== null) {
65
85
  this.insertSchemaSubschemas(schema[key], rootSchemaId)
package/lib/serializer.js CHANGED
@@ -1,5 +1,8 @@
1
1
  'use strict'
2
2
 
3
+ // eslint-disable-next-line
4
+ const STR_ESCAPE = /[\u0000-\u001f\u0022\u005c\ud800-\udfff]|[\ud800-\udbff](?![\udc00-\udfff])|(?:[^\ud800-\udbff]|^)[\udc00-\udfff]/
5
+
3
6
  module.exports = class Serializer {
4
7
  constructor (options = {}) {
5
8
  switch (options.rounding) {
@@ -94,6 +97,11 @@ module.exports = class Serializer {
94
97
  str = str.toString()
95
98
  }
96
99
 
100
+ // Fast escape chars check
101
+ if (!STR_ESCAPE.test(str)) {
102
+ return quotes + str + quotes
103
+ }
104
+
97
105
  if (str.length < 42) {
98
106
  return this.asStringSmall(str)
99
107
  } else {
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "fast-json-stringify",
3
- "version": "5.4.0",
3
+ "version": "5.5.0",
4
4
  "description": "Stringify your JSON at max speed",
5
5
  "main": "index.js",
6
- "types": "index.d.ts",
6
+ "types": "types/index.d.ts",
7
7
  "scripts": {
8
8
  "bench": "node ./benchmark/bench.js",
9
9
  "bench:cmp": "node ./benchmark/bench-cmp-branch.js",
@@ -11,7 +11,7 @@
11
11
  "benchmark": "node ./benchmark/bench-cmp-lib.js",
12
12
  "lint": "standard",
13
13
  "lint:fix": "standard --fix",
14
- "test:typescript": "tsc --project ./test/types/tsconfig.json && tsd",
14
+ "test:typescript": "tsd",
15
15
  "test:unit": "tap -J test/*.test.js test/**/*.test.js",
16
16
  "test": "npm run test:unit && npm run test:typescript"
17
17
  },
@@ -34,7 +34,7 @@
34
34
  "homepage": "https://github.com/fastify/fast-json-stringify#readme",
35
35
  "devDependencies": {
36
36
  "@fastify/pre-commit": "^2.0.2",
37
- "@sinclair/typebox": "^0.24.9",
37
+ "@sinclair/typebox": "^0.25.2",
38
38
  "benchmark": "^2.1.4",
39
39
  "cli-select": "^1.1.2",
40
40
  "compile-json-stringify": "^0.1.2",
@@ -46,7 +46,6 @@
46
46
  "standard": "^17.0.0",
47
47
  "tap": "^16.0.1",
48
48
  "tsd": "^0.24.1",
49
- "typescript": "^4.0.2",
50
49
  "webpack": "^5.40.0"
51
50
  },
52
51
  "dependencies": {
@@ -62,8 +61,5 @@
62
61
  "schema-validator.js"
63
62
  ]
64
63
  },
65
- "runkitExampleFilename": "./examples/example.js",
66
- "tsd": {
67
- "directory": "test/types"
68
- }
64
+ "runkitExampleFilename": "./examples/example.js"
69
65
  }
@@ -616,3 +616,31 @@ test('anyOf with a nested external schema', (t) => {
616
616
  const stringify = build(schema, { schema: externalSchemas })
617
617
  t.equal(stringify('foo'), '"foo"')
618
618
  })
619
+
620
+ test('object with ref and validated properties', (t) => {
621
+ t.plan(1)
622
+
623
+ const externalSchemas = {
624
+ RefSchema: {
625
+ $id: 'RefSchema',
626
+ type: 'string'
627
+ }
628
+ }
629
+
630
+ const schema = {
631
+ $id: 'root',
632
+ type: 'object',
633
+ properties: {
634
+ id: {
635
+ anyOf: [
636
+ { type: 'string' },
637
+ { type: 'number' }
638
+ ]
639
+ },
640
+ reference: { $ref: 'RefSchema' }
641
+ }
642
+ }
643
+
644
+ const stringify = build(schema, { schema: externalSchemas })
645
+ t.equal(stringify({ id: 1, reference: 'hi' }), '{"id":1,"reference":"hi"}')
646
+ })
@@ -1,5 +1,7 @@
1
1
  import Ajv, { Options as AjvOptions } from "ajv"
2
2
 
3
+ type Build = typeof build
4
+
3
5
  declare namespace build {
4
6
  interface BaseSchema {
5
7
  /**
@@ -168,6 +170,12 @@ declare namespace build {
168
170
  */
169
171
  mode?: 'debug' | 'standalone'
170
172
  }
173
+
174
+ export const validLargeArrayMechanisms: string[]
175
+ export function restore (value: <TDoc extends object = object>(doc: TDoc) => string): ReturnType<Build>
176
+
177
+ export const build: Build
178
+ export { build as default }
171
179
  }
172
180
 
173
181
  interface DebugOption extends build.Options {
@@ -1,5 +1,6 @@
1
1
  import Ajv from 'ajv'
2
- import build, { Schema } from '../..'
2
+ import build, { restore, Schema, validLargeArrayMechanisms } from '..'
3
+ import { expectError, expectType } from 'tsd'
3
4
 
4
5
  // Number schemas
5
6
  const schema1: Schema = {
@@ -150,4 +151,69 @@ str = build(schema1, { debugMode: true }).code
150
151
  ajv = build(schema1, { debugMode: true }).ajv
151
152
  str = build(schema1, { mode: 'debug' }).code
152
153
  ajv = build(schema1, { mode: 'debug' }).ajv
153
- str = build(schema1, { mode: 'standalone' })
154
+ str = build(schema1, { mode: 'standalone' })
155
+
156
+ const debugCompiled = build({
157
+ title: 'default string',
158
+ type: 'object',
159
+ properties: {
160
+ firstName: {
161
+ type: 'string'
162
+ }
163
+ }
164
+ }, { mode: 'debug' })
165
+ expectType<ReturnType<typeof build>>(build.restore(debugCompiled))
166
+ expectType<ReturnType<typeof build>>(restore(debugCompiled))
167
+
168
+ expectType<string[]>(build.validLargeArrayMechanisms)
169
+ expectType<string[]>(validLargeArrayMechanisms)
170
+
171
+ /**
172
+ * Schema inference
173
+ */
174
+
175
+ // With inference
176
+ interface InferenceSchema {
177
+ id: string;
178
+ a?: number;
179
+ }
180
+
181
+ const stringify3 = build({
182
+ type: "object",
183
+ properties: { a: { type: "string" } },
184
+ });
185
+ stringify3<InferenceSchema>({ id: "123" });
186
+ stringify3<InferenceSchema>({ a: 123, id: "123" });
187
+ expectError(stringify3<InferenceSchema>({ anotherOne: "bar" }));
188
+ expectError(stringify3<Schema>({ a: "bar" }));
189
+
190
+ // Without inference
191
+ const stringify4 = build({
192
+ type: "object",
193
+ properties: { a: { type: "string" } },
194
+ });
195
+ stringify4({ id: "123" });
196
+ stringify4({ a: 123, id: "123" });
197
+ stringify4({ anotherOne: "bar" });
198
+ stringify4({ a: "bar" });
199
+
200
+ // Without inference - string type
201
+ const stringify5 = build({
202
+ type: "string",
203
+ });
204
+ stringify5("foo");
205
+ expectError(stringify5({ id: "123" }));
206
+
207
+ // Without inference - null type
208
+ const stringify6 = build({
209
+ type: "null",
210
+ });
211
+ stringify6(null);
212
+ expectError(stringify6("a string"));
213
+
214
+ // Without inference - boolean type
215
+ const stringify7 = build({
216
+ type: "boolean",
217
+ });
218
+ stringify7(true);
219
+ expectError(stringify7("a string"));
@@ -1,48 +0,0 @@
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"));
@@ -1,13 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "esModuleInterop": true,
4
- "noEmit": true,
5
- "rootDir": "../..",
6
- "strict": true,
7
- "target": "ES2015",
8
- "moduleResolution": "node"
9
- },
10
- "include": [
11
- "./test.ts"
12
- ]
13
- }