fast-json-stringify 2.7.8 → 2.7.12

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,111 @@
1
+ name: Benchmark
2
+
3
+ on:
4
+ pull_request_target:
5
+ types: [labeled]
6
+
7
+ jobs:
8
+ benchmark:
9
+ if: ${{ github.event.label.name == 'benchmark' }}
10
+ runs-on: ubuntu-latest
11
+ outputs:
12
+ PR-BENCH-12: ${{ steps.benchmark-pr.outputs.BENCH_RESULT12 }}
13
+ PR-BENCH-14: ${{ steps.benchmark-pr.outputs.BENCH_RESULT14 }}
14
+ PR-BENCH-16: ${{ steps.benchmark-pr.outputs.BENCH_RESULT16 }}
15
+ MAIN-BENCH-12: ${{ steps.benchmark-main.outputs.BENCH_RESULT12 }}
16
+ MAIN-BENCH-14: ${{ steps.benchmark-main.outputs.BENCH_RESULT14 }}
17
+ MAIN-BENCH-16: ${{ steps.benchmark-main.outputs.BENCH_RESULT16 }}
18
+ strategy:
19
+ matrix:
20
+ node-version: [12, 14, 16]
21
+ steps:
22
+ - uses: actions/checkout@v2
23
+ with:
24
+ ref: ${{github.event.pull_request.head.ref}}
25
+ repository: ${{github.event.pull_request.head.repo.full_name}}
26
+
27
+ - uses: actions/setup-node@v2
28
+ with:
29
+ node-version: ${{ matrix.node-version }}
30
+
31
+ - name: Install
32
+ run: |
33
+ npm install --ignore-scripts
34
+
35
+ - name: Run benchmark
36
+ id: benchmark-pr
37
+ run: |
38
+ npm run --silent benchmark > ./bench-result
39
+ content=$(cat ./bench-result)
40
+ content="${content//'%'/'%25'}"
41
+ content="${content//$'\n'/'%0A'}"
42
+ content="${content//$'\r'/'%0D'}"
43
+ echo "::set-output name=BENCH_RESULT${{matrix.node-version}}::$content"
44
+
45
+ # main benchmark
46
+ - uses: actions/checkout@v2
47
+ with:
48
+ ref: 'master'
49
+
50
+ - name: Install
51
+ run: |
52
+ npm install --ignore-scripts
53
+
54
+ - name: Run benchmark
55
+ id: benchmark-master
56
+ run: |
57
+ npm run --silent benchmark > ./bench-result
58
+ content=$(cat ./bench-result)
59
+ content="${content//'%'/'%25'}"
60
+ content="${content//$'\n'/'%0A'}"
61
+ content="${content//$'\r'/'%0D'}"
62
+ echo "::set-output name=BENCH_RESULT${{matrix.node-version}}::$content"
63
+
64
+ output-benchmark:
65
+ needs: [benchmark]
66
+ runs-on: ubuntu-latest
67
+ steps:
68
+ - name: Comment PR
69
+ uses: thollander/actions-comment-pull-request@v1
70
+ with:
71
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
72
+ message: |
73
+ **Node**: 12
74
+ **PR**:
75
+ ```
76
+ ${{ needs.benchmark.outputs.PR-BENCH-12 }}
77
+ ```
78
+ **MAIN**:
79
+ ```
80
+ ${{ needs.benchmark.outputs.MAIN-BENCH-12 }}
81
+ ```
82
+
83
+ ---
84
+
85
+ **Node**: 14
86
+ **PR**:
87
+ ```
88
+ ${{ needs.benchmark.outputs.PR-BENCH-14 }}
89
+ ```
90
+ **MAIN**:
91
+ ```
92
+ ${{ needs.benchmark.outputs.MAIN-BENCH-14 }}
93
+ ```
94
+
95
+ ---
96
+
97
+ **Node**: 16
98
+ **PR**:
99
+ ```
100
+ ${{ needs.benchmark.outputs.PR-BENCH-16 }}
101
+ ```
102
+ **MAIN**:
103
+ ```
104
+ ${{ needs.benchmark.outputs.MAIN-BENCH-16 }}
105
+ ```
106
+
107
+ - uses: actions-ecosystem/action-remove-labels@v1
108
+ with:
109
+ labels: |
110
+ benchmark
111
+ github_token: ${{ secrets.GITHUB_TOKEN }}
@@ -22,7 +22,7 @@ jobs:
22
22
  - uses: actions/checkout@v2.3.4
23
23
 
24
24
  - name: Use Node.js
25
- uses: actions/setup-node@v2.3.0
25
+ uses: actions/setup-node@v2.4.0
26
26
  with:
27
27
  node-version: ${{ matrix.node-version }}
28
28
 
@@ -38,6 +38,6 @@ jobs:
38
38
  needs: test
39
39
  runs-on: ubuntu-latest
40
40
  steps:
41
- - uses: fastify/github-action-merge-dependabot@v2.2.0
41
+ - uses: fastify/github-action-merge-dependabot@v2.5.0
42
42
  with:
43
43
  github-token: ${{ secrets.GITHUB_TOKEN }}
package/README.md CHANGED
@@ -12,22 +12,25 @@ __fast-json-stringify__ is significantly faster than `JSON.stringify()` for smal
12
12
  ##### Benchmarks
13
13
 
14
14
  - Machine: `EX41S-SSD, Intel Core i7, 4Ghz, 64GB RAM, 4C/8T, SSD`.
15
- - Node.js `v12.16.2`
15
+ - Node.js `v16.9.1`
16
16
 
17
17
  ```
18
- FJS creation x 59,805 ops/sec ±0.23% (91 runs sampled)
18
+ FJS creation x 6,040 ops/sec ±1.17% (91 runs sampled)
19
19
 
20
- JSON.stringify array x 5,330 ops/sec ±0.54% (97 runs sampled)
21
- fast-json-stringify array x 6,995 ops/sec ±0.24% (94 runs sampled)
20
+ JSON.stringify array x 5,519 ops/sec ±0.08% (99 runs sampled)
21
+ fast-json-stringify array x 7,143 ops/sec ±0.14% (97 runs sampled)
22
22
 
23
- JSON.stringify long string x 15,108 ops/sec ±0.13% (100 runs sampled)
24
- fast-json-stringify long string x 15,089 ops/sec ±0.15% (98 runs sampled)
23
+ JSON.stringify long string x 16,438 ops/sec ±0.32% (98 runs sampled)
24
+ fast-json-stringify long string x 16,457 ops/sec ±0.09% (97 runs sampled)
25
25
 
26
- JSON.stringify short string x 13,214,696 ops/sec ±0.19% (97 runs sampled)
27
- fast-json-stringify short string x 33,378,500 ops/sec ±0.27% (95 runs sampled)
26
+ JSON.stringify short string x 12,061,258 ops/sec ±0.32% (97 runs sampled)
27
+ fast-json-stringify short string x 35,531,071 ops/sec ±0.17% (94 runs sampled)
28
28
 
29
- JSON.stringify obj x 3,172,653 ops/sec ±0.15% (98 runs sampled)
30
- fast-json-stringify obj x 13,537,123 ops/sec ±0.19% (95 runs sampled)
29
+ JSON.stringify obj x 3,079,746 ops/sec ±0.09% (95 runs sampled)
30
+ fast-json-stringify obj x 7,721,569 ops/sec ±0.12% (98 runs sampled)
31
+
32
+ JSON stringify date x 1,149,786 ops/sec ±0.10% (99 runs sampled)
33
+ fast-json-stringify date format x 1,674,498 ops/sec ±0.12% (99 runs sampled)
31
34
  ```
32
35
 
33
36
  #### Table of contents:
package/bench.js CHANGED
@@ -50,22 +50,38 @@ const arraySchemaCJS = {
50
50
  items: schemaCJS
51
51
  }
52
52
 
53
+ const dateFormatSchema = {
54
+ description: 'Date of birth',
55
+ type: 'string',
56
+ format: 'date'
57
+ }
58
+
59
+ const dateFormatSchemaCJS = {
60
+ description: 'Date of birth',
61
+ type: 'string',
62
+ format: 'date'
63
+ }
64
+
53
65
  const obj = {
54
66
  firstName: 'Matteo',
55
67
  lastName: 'Collina',
56
68
  age: 32
57
69
  }
58
70
 
71
+ const date = new Date()
72
+
59
73
  const multiArray = []
60
74
 
61
75
  const CJS = require('compile-json-stringify')
62
76
  const CJSStringify = CJS(schemaCJS)
63
77
  const CJSStringifyArray = CJS(arraySchemaCJS)
78
+ const CJSStringifyDate = CJS(dateFormatSchemaCJS)
64
79
  const CJSStringifyString = CJS({ type: 'string' })
65
80
 
66
81
  const FJS = require('.')
67
82
  const stringify = FJS(schema)
68
83
  const stringifyArray = FJS(arraySchema)
84
+ const stringifyDate = FJS(dateFormatSchema)
69
85
  const stringifyString = FJS({ type: 'string' })
70
86
  let str = ''
71
87
 
@@ -138,6 +154,18 @@ suite.add('compile-json-stringify obj', function () {
138
154
  CJSStringify(obj)
139
155
  })
140
156
 
157
+ suite.add('JSON stringify date', function () {
158
+ JSON.stringify(date)
159
+ })
160
+
161
+ suite.add('fast-json-stringify date format', function () {
162
+ stringifyDate(date)
163
+ })
164
+
165
+ suite.add('compile-json-stringify date format', function () {
166
+ CJSStringifyDate(date)
167
+ })
168
+
141
169
  suite.on('cycle', cycle)
142
170
 
143
171
  suite.run()
package/index.d.ts CHANGED
@@ -64,6 +64,7 @@ declare namespace build {
64
64
 
65
65
  export interface StringSchema extends BaseSchema {
66
66
  type: "string";
67
+ format?: string;
67
68
  }
68
69
 
69
70
  export interface IntegerSchema extends BaseSchema {
package/index.js CHANGED
@@ -47,7 +47,12 @@ function mergeLocation (source, dest) {
47
47
  }
48
48
  }
49
49
 
50
+ const arrayItemsReferenceSerializersMap = new Map()
51
+ const objectReferenceSerializersMap = new Map()
52
+
50
53
  function build (schema, options) {
54
+ arrayItemsReferenceSerializersMap.clear()
55
+ objectReferenceSerializersMap.clear()
51
56
  options = options || {}
52
57
  isValidSchema(schema)
53
58
  if (options.schema) {
@@ -144,6 +149,9 @@ function build (schema, options) {
144
149
  return dependenciesName
145
150
  }
146
151
 
152
+ arrayItemsReferenceSerializersMap.clear()
153
+ objectReferenceSerializersMap.clear()
154
+
147
155
  return (Function.apply(null, dependenciesName).apply(null, dependencies))
148
156
  }
149
157
 
@@ -285,10 +293,7 @@ function $asDatetime (date, skipQuotes) {
285
293
  function $asDate (date, skipQuotes) {
286
294
  const quotes = skipQuotes === true ? '' : '"'
287
295
  if (date instanceof Date) {
288
- const year = new Intl.DateTimeFormat('en', { year: 'numeric' }).format(date)
289
- const month = new Intl.DateTimeFormat('en', { month: '2-digit' }).format(date)
290
- const day = new Intl.DateTimeFormat('en', { day: '2-digit' }).format(date)
291
- return quotes + year + '-' + month + '-' + day + quotes
296
+ return quotes + new Date(date.getTime() - (date.getTimezoneOffset() * 60000 )).toISOString().slice(0, 10) + quotes
292
297
  } else if (date && typeof date.format === 'function') {
293
298
  return quotes + date.format('YYYY-MM-DD') + quotes
294
299
  } else {
@@ -397,51 +402,60 @@ function addPatternProperties (location) {
397
402
  } catch (err) {
398
403
  throw new Error(`${err.message}. Found at ${regex} matching ${JSON.stringify(pp[regex])}`)
399
404
  }
400
- code += `
401
- if (/${regex.replace(/\\*\//g, '\\/')}/.test(keys[i])) {
402
- `
405
+
406
+ const ifPpKeyExists = `if (/${regex.replace(/\\*\//g, '\\/')}/.test(keys[i])) {`
407
+
403
408
  if (type === 'object') {
404
409
  code += `${buildObject(ppLocation, '', 'buildObjectPP' + index)}
410
+ ${ifPpKeyExists}
405
411
  ${addComma}
406
412
  json += $asString(keys[i]) + ':' + buildObjectPP${index}(obj[keys[i]])
407
413
  `
408
414
  } else if (type === 'array') {
409
415
  code += `${buildArray(ppLocation, '', 'buildArrayPP' + index)}
416
+ ${ifPpKeyExists}
410
417
  ${addComma}
411
418
  json += $asString(keys[i]) + ':' + buildArrayPP${index}(obj[keys[i]])
412
419
  `
413
420
  } else if (type === 'null') {
414
421
  code += `
422
+ ${ifPpKeyExists}
415
423
  ${addComma}
416
424
  json += $asString(keys[i]) +':null'
417
425
  `
418
426
  } else if (type === 'string') {
419
427
  code += `
428
+ ${ifPpKeyExists}
420
429
  ${addComma}
421
430
  json += $asString(keys[i]) + ':' + ${stringSerializer}(obj[keys[i]])
422
431
  `
423
432
  } else if (type === 'integer') {
424
433
  code += `
434
+ ${ifPpKeyExists}
425
435
  ${addComma}
426
436
  json += $asString(keys[i]) + ':' + $asInteger(obj[keys[i]])
427
437
  `
428
438
  } else if (type === 'number') {
429
439
  code += `
440
+ ${ifPpKeyExists}
430
441
  ${addComma}
431
442
  json += $asString(keys[i]) + ':' + $asNumber(obj[keys[i]])
432
443
  `
433
444
  } else if (type === 'boolean') {
434
445
  code += `
446
+ ${ifPpKeyExists}
435
447
  ${addComma}
436
448
  json += $asString(keys[i]) + ':' + $asBoolean(obj[keys[i]])
437
449
  `
438
450
  } else if (type === undefined) {
439
451
  code += `
452
+ ${ifPpKeyExists}
440
453
  ${addComma}
441
454
  json += $asString(keys[i]) + ':' + $asAny(obj[keys[i]])
442
455
  `
443
456
  } else {
444
457
  code += `
458
+ ${ifPpKeyExists}
445
459
  throw new Error('Cannot coerce ' + obj[keys[i]] + ' to ' + ${JSON.stringify(type)})
446
460
  `
447
461
  }
@@ -920,6 +934,16 @@ function buildObject (location, code, name) {
920
934
  }
921
935
  `
922
936
  }
937
+
938
+ if (objectReferenceSerializersMap.has(schema)) {
939
+ code += `
940
+ return ${objectReferenceSerializersMap.get(schema)}(input)
941
+ }
942
+ `
943
+ return code
944
+ }
945
+ objectReferenceSerializersMap.set(schema, name)
946
+
923
947
  code += `
924
948
  var obj = ${toJSON('input')}
925
949
  var json = '{'
@@ -959,9 +983,6 @@ function buildArray (location, code, name, key = null) {
959
983
  }
960
984
  `
961
985
  }
962
- code += `
963
- var json = '['
964
- `
965
986
  const laterCode = ''
966
987
 
967
988
  // default to any items type
@@ -975,8 +996,18 @@ function buildArray (location, code, name, key = null) {
975
996
  schema = location.schema
976
997
  schema[fjsCloned] = true
977
998
  }
999
+
978
1000
  location = refFinder(schema.items.$ref, location)
979
1001
  schema.items = location.schema
1002
+
1003
+ if (arrayItemsReferenceSerializersMap.has(schema.items)) {
1004
+ code += `
1005
+ return ${arrayItemsReferenceSerializersMap.get(schema.items)}(obj)
1006
+ }
1007
+ `
1008
+ return code
1009
+ }
1010
+ arrayItemsReferenceSerializersMap.set(schema.items, name)
980
1011
  }
981
1012
 
982
1013
  let result = { code: '', laterCode: '' }
@@ -1023,17 +1054,17 @@ function buildArray (location, code, name, key = null) {
1023
1054
 
1024
1055
  code += `
1025
1056
  var l = obj.length
1026
-
1057
+ var jsonOutput= ''
1027
1058
  for (var i = 0; i < l; i++) {
1028
- var jsonLastChar = json[json.length - 1]
1059
+ var json = ''
1060
+ ${result.code}
1061
+ jsonOutput += json
1029
1062
 
1030
- if (i > 0 && jsonLastChar !== '[' && jsonLastChar !== ',') {
1031
- json += ','
1063
+ if (json.length > 0 && i < l - 1) {
1064
+ jsonOutput += ','
1032
1065
  }
1033
- ${result.code}
1034
1066
  }
1035
- json += ']'
1036
- return json
1067
+ return \`[\${jsonOutput}]\`
1037
1068
  }
1038
1069
  ${result.laterCode}
1039
1070
  `
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fast-json-stringify",
3
- "version": "2.7.8",
3
+ "version": "2.7.12",
4
4
  "description": "Stringify your JSON at max speed",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -0,0 +1,180 @@
1
+ 'use strict'
2
+
3
+ const test = require('tap').test
4
+ const build = require('..')
5
+
6
+ test('can stringify recursive directory tree (issue #181)', (t) => {
7
+ t.plan(1)
8
+
9
+ const schema = {
10
+ definitions: {
11
+ directory: {
12
+ type: 'object',
13
+ properties: {
14
+ name: { type: 'string' },
15
+ subDirectories: {
16
+ type: 'array',
17
+ items: { $ref: '#/definitions/directory' },
18
+ default: []
19
+ }
20
+ }
21
+ }
22
+ },
23
+ type: 'array',
24
+ items: { $ref: '#/definitions/directory' }
25
+ }
26
+ const stringify = build(schema)
27
+
28
+ t.equal(stringify([
29
+ { name: 'directory 1', subDirectories: [] },
30
+ {
31
+ name: 'directory 2',
32
+ subDirectories: [
33
+ { name: 'directory 2.1', subDirectories: [] },
34
+ { name: 'directory 2.2', subDirectories: [] }
35
+ ]
36
+ }
37
+ ]), '[{"name":"directory 1","subDirectories":[]},{"name":"directory 2","subDirectories":[{"name":"directory 2.1","subDirectories":[]},{"name":"directory 2.2","subDirectories":[]}]}]')
38
+ })
39
+
40
+ test('can stringify when recursion in external schema', t => {
41
+ t.plan(1)
42
+
43
+ const referenceSchema = {
44
+ $id: 'person',
45
+ type: 'object',
46
+ properties: {
47
+ name: { type: 'string' },
48
+ children: {
49
+ type: 'array',
50
+ items: { $ref: '#' }
51
+ }
52
+ }
53
+ }
54
+
55
+ const schema = {
56
+ $id: 'mainSchema',
57
+ type: 'object',
58
+ properties: {
59
+ people: {
60
+ $ref: 'person'
61
+ }
62
+ }
63
+ }
64
+ const stringify = build(schema, {
65
+ schema: {
66
+ [referenceSchema.$id]: referenceSchema
67
+ }
68
+ })
69
+
70
+ const value = stringify({ people: { name: 'Elizabeth', children: [{ name: 'Charles' }] } })
71
+ t.equal(value, '{"people":{"name":"Elizabeth","children":[{"name":"Charles"}]}}')
72
+ })
73
+
74
+ test('use proper serialize function', t => {
75
+ t.plan(1)
76
+
77
+ const personSchema = {
78
+ $id: 'person',
79
+ type: 'object',
80
+ properties: {
81
+ name: { type: 'string' },
82
+ children: {
83
+ type: 'array',
84
+ items: { $ref: '#' }
85
+ }
86
+ }
87
+ }
88
+
89
+ const directorySchema = {
90
+ $id: 'directory',
91
+ type: 'object',
92
+ properties: {
93
+ name: { type: 'string' },
94
+ subDirectories: {
95
+ type: 'array',
96
+ items: { $ref: '#' },
97
+ default: []
98
+ }
99
+ }
100
+ }
101
+
102
+ const schema = {
103
+ $id: 'mainSchema',
104
+ type: 'object',
105
+ properties: {
106
+ people: { $ref: 'person' },
107
+ directory: { $ref: 'directory' }
108
+ }
109
+ }
110
+ const stringify = build(schema, {
111
+ schema: {
112
+ [personSchema.$id]: personSchema,
113
+ [directorySchema.$id]: directorySchema
114
+ }
115
+ })
116
+
117
+ const value = stringify({
118
+ people: {
119
+ name: 'Elizabeth',
120
+ children: [{
121
+ name: 'Charles',
122
+ children: [{ name: 'William', children: [{ name: 'George' }, { name: 'Charlotte' }] }, { name: 'Harry' }]
123
+ }]
124
+ },
125
+ directory: {
126
+ name: 'directory 1',
127
+ subDirectories: [
128
+ { name: 'directory 1.1', subDirectories: [] },
129
+ {
130
+ name: 'directory 1.2',
131
+ subDirectories: [{ name: 'directory 1.2.1' }, { name: 'directory 1.2.2' }]
132
+ }
133
+ ]
134
+ }
135
+ })
136
+ t.equal(value, '{"people":{"name":"Elizabeth","children":[{"name":"Charles","children":[{"name":"William","children":[{"name":"George"},{"name":"Charlotte"}]},{"name":"Harry"}]}]},"directory":{"name":"directory 1","subDirectories":[{"name":"directory 1.1","subDirectories":[]},{"name":"directory 1.2","subDirectories":[{"name":"directory 1.2.1","subDirectories":[]},{"name":"directory 1.2.2","subDirectories":[]}]}]}}')
137
+ })
138
+
139
+ test('can stringify recursive references in object types (issue #365)', t => {
140
+ t.plan(1)
141
+
142
+ const schema = {
143
+ type: 'object',
144
+ definitions: {
145
+ parentCategory: {
146
+ type: 'object',
147
+ properties: {
148
+ parent: {
149
+ $ref: '#/definitions/parentCategory'
150
+ }
151
+ }
152
+ }
153
+ },
154
+ properties: {
155
+ category: {
156
+ type: 'object',
157
+ properties: {
158
+ parent: {
159
+ $ref: '#/definitions/parentCategory'
160
+ }
161
+ }
162
+ }
163
+ }
164
+ }
165
+
166
+ const stringify = build(schema)
167
+ const data = {
168
+ category: {
169
+ parent: {
170
+ parent: {
171
+ parent: {
172
+ parent: {}
173
+ }
174
+ }
175
+ }
176
+ }
177
+ }
178
+ const value = stringify(data)
179
+ t.equal(value, '{"category":{"parent":{"parent":{"parent":{"parent":{}}}}}}')
180
+ })
@@ -135,3 +135,11 @@ const schema11: Schema = {
135
135
  }
136
136
 
137
137
  build(schema11)({ something: 'a string', somethingElse: 42 })
138
+
139
+ // String schema with format
140
+ const schema12: Schema = {
141
+ type: 'string',
142
+ format: 'date-time'
143
+ }
144
+
145
+ build(schema12)(new Date())