fast-json-stringify 1.17.0 → 1.20.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.
@@ -0,0 +1,8 @@
1
+ version: 1
2
+ update_configs:
3
+ - package_manager: "javascript"
4
+ directory: "/"
5
+ update_schedule: "daily"
6
+ ignored_updates:
7
+ - match:
8
+ dependency_name: "tap"
@@ -0,0 +1,18 @@
1
+ name: CI workflow
2
+ on: [push, pull_request]
3
+ jobs:
4
+ test:
5
+ runs-on: ubuntu-latest
6
+ strategy:
7
+ matrix:
8
+ node-version: [6.x, 8.x, 10.x, 11.x, 12.x, 13.x]
9
+ steps:
10
+ - uses: actions/checkout@v2
11
+ - name: Use Node.js ${{ matrix.node-version }}
12
+ uses: actions/setup-node@v1
13
+ with:
14
+ node-version: ${{ matrix.node-version }}
15
+ - name: Install Dependencies
16
+ run: npm install --ignore-scripts
17
+ - name: Test
18
+ run: npm run test
package/README.md CHANGED
@@ -1,6 +1,9 @@
1
1
  # fast-json-stringify
2
2
 
3
- [![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat)](http://standardjs.com/) [![Build Status](https://dev.azure.com/fastify/fastify/_apis/build/status/fastify.fast-json-stringify?branchName=master)](https://dev.azure.com/fastify/fastify/_build/latest?definitionId=3&branchName=master) [![Build Status](https://travis-ci.org/fastify/fast-json-stringify.svg?branch=master)](https://travis-ci.org/fastify/fast-json-stringify) [![NPM downloads](https://img.shields.io/npm/dm/fast-json-stringify.svg?style=flat)](https://www.npmjs.com/package/fast-json-stringify)
3
+ [![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat)](http://standardjs.com/)
4
+ ![Ci Workflow](https://github.com/fastify/fast-json-stringify/workflows/CI%20workflow/badge.svg)
5
+ [![NPM downloads](https://img.shields.io/npm/dm/fast-json-stringify.svg?style=flat)](https://www.npmjs.com/package/fast-json-stringify)
6
+
4
7
 
5
8
  __fast-json-stringify__ is significantly faster than `JSON.stringify()` for small payloads. Its performance advantage shrinks as your payload grows. It pairs well with [__flatstr__](https://www.npmjs.com/package/flatstr), which triggers a V8 optimization that improves performance when eventually converting the string to a `Buffer`.
6
9
 
@@ -102,11 +105,34 @@ And nested ones, too.
102
105
  <a name="specific"></a>
103
106
  #### Specific use cases
104
107
 
105
- | Instance | Serialized as |
106
- | -----------|------------------------------|
107
- | `Date` | `string` via `toISOString()` |
108
- | `RegExp` | `string` |
109
- | `BigInt` | `integer` via `toString` |
108
+ | Instance | Serialized as |
109
+ | -------- | ---------------------------- |
110
+ | `Date` | `string` via `toISOString()` |
111
+ | `RegExp` | `string` |
112
+ | `BigInt` | `integer` via `toString` |
113
+
114
+ [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:
115
+
116
+ | Format | Serialized format example |
117
+ | ----------- | -------------------------- |
118
+ | `date-time` | `2020-04-03T09:11:08.615Z` |
119
+ | `date` | `2020-04-03` |
120
+ | `time` | `09:11:08` |
121
+
122
+ Example with a MomentJS object:
123
+
124
+ ```javascript
125
+ const moment = require('moment')
126
+
127
+ const stringify = fastJson({
128
+ title: 'Example Schema with string date-time field',
129
+ type: 'string',
130
+ format: 'date-time'
131
+ }
132
+
133
+ console.log(stringify(moment())) // '"YYYY-MM-DDTHH:mm:ss.sssZ"'
134
+ ```
135
+
110
136
 
111
137
  <a name="required"></a>
112
138
  #### Required
@@ -441,7 +467,7 @@ const stringify = fastJson(schema, { schema: externalSchema })
441
467
 
442
468
  <a name="long"></a>
443
469
  #### Long integers
444
- By default the library will handle automatically [BigInt](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt) from Node.js v10.3 and above.
470
+ By default the library will handle automatically [BigInt](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt) from Node.js v10.3 and above.
445
471
  If you can't use BigInts in your environment, long integers (64-bit) are also supported using the [long](https://github.com/dcodeIO/long.js) module.
446
472
  Example:
447
473
  ```javascript
@@ -552,6 +578,31 @@ allowing user input to directly supply a schema.
552
578
  It can't be guaranteed that allowing user input for the schema couldn't feasibly expose an attack
553
579
  vector.
554
580
 
581
+ <a name="debug"></a>
582
+ ### Debug Mode
583
+
584
+ The debug mode can be activated during your development to understand what is going on when things do not
585
+ work as you expect.
586
+
587
+ ```js
588
+ const debugCompiled = fastJson({
589
+ title: 'default string',
590
+ type: 'object',
591
+ properties: {
592
+ firstName: {
593
+ type: 'string'
594
+ }
595
+ }
596
+ }, { debugMode: true })
597
+
598
+ console.log(debugCompiled) // it is an array of functions that can create your `stringify` function
599
+ console.log(debugCompiled.toString()) // print a "ready to read" string function, you can save it to a file
600
+
601
+ const rawString = debugCompiled.toString()
602
+ const stringify = fastJson.restore(rawString) // use the generated string to get back the `stringify` function
603
+ console.log(stringify({ firstName: 'Foo', surname: 'bar' })) // '{"firstName":"Foo"}'
604
+ ```
605
+
555
606
  <a name="acknowledgements"></a>
556
607
  ## Acknowledgements
557
608
 
package/example.js CHANGED
@@ -1,5 +1,6 @@
1
1
  'use strict'
2
2
 
3
+ const moment = require('moment')
3
4
  const fastJson = require('.')
4
5
  const stringify = fastJson({
5
6
  title: 'Example Schema',
@@ -18,6 +19,10 @@ const stringify = fastJson({
18
19
  now: {
19
20
  type: 'string'
20
21
  },
22
+ birthdate: {
23
+ type: ['string'],
24
+ format: 'date-time'
25
+ },
21
26
  reg: {
22
27
  type: 'string'
23
28
  },
@@ -48,6 +53,10 @@ const stringify = fastJson({
48
53
  },
49
54
  test: {
50
55
  type: 'number'
56
+ },
57
+ date: {
58
+ type: 'string',
59
+ format: 'date-time'
51
60
  }
52
61
  },
53
62
  additionalProperties: {
@@ -60,10 +69,12 @@ console.log(stringify({
60
69
  lastName: 'Collina',
61
70
  age: 32,
62
71
  now: new Date(),
72
+ birthdate: moment(),
63
73
  reg: /"([^"]|\\")*"/,
64
74
  foo: 'hello',
65
75
  numfoo: 42,
66
76
  test: 42,
77
+ date: moment(),
67
78
  strtest: '23',
68
79
  arr: [{ str: 'stark' }, { str: 'lannister' }],
69
80
  obj: { bool: true },
package/index.js CHANGED
@@ -4,6 +4,7 @@
4
4
 
5
5
  var Ajv = require('ajv')
6
6
  var merge = require('deepmerge')
7
+
7
8
  var util = require('util')
8
9
  var validate = require('./schema-validator')
9
10
  var stringSimilarity = null
@@ -52,9 +53,13 @@ function build (schema, options) {
52
53
  `
53
54
 
54
55
  code += `
56
+ ${$pad2Zeros.toString()}
55
57
  ${$asString.toString()}
56
58
  ${$asStringNullable.toString()}
57
59
  ${$asStringSmall.toString()}
60
+ ${$asDatetime.toString()}
61
+ ${$asDate.toString()}
62
+ ${$asTime.toString()}
58
63
  ${$asNumber.toString()}
59
64
  ${$asNumberNullable.toString()}
60
65
  ${$asIntegerNullable.toString()}
@@ -94,7 +99,7 @@ function build (schema, options) {
94
99
  code = buildObject(schema, code, main, options.schema, fullSchema)
95
100
  break
96
101
  case 'string':
97
- main = schema.nullable ? $asStringNullable.name : $asString.name
102
+ main = schema.nullable ? $asStringNullable.name : getStringSerializer(schema.format)
98
103
  break
99
104
  case 'integer':
100
105
  main = schema.nullable ? $asIntegerNullable.name : $asInteger.name
@@ -127,12 +132,20 @@ function build (schema, options) {
127
132
 
128
133
  var dependencies = []
129
134
  var dependenciesName = []
130
- if (hasAnyOf(schema) || hasSchemaSomeIf) {
135
+ if (hasOf(schema) || hasSchemaSomeIf) {
131
136
  dependencies.push(new Ajv(options.ajv))
132
137
  dependenciesName.push('ajv')
133
138
  }
134
139
 
135
140
  dependenciesName.push(code)
141
+
142
+ if (options.debugMode) {
143
+ dependenciesName.toString = function () {
144
+ return dependenciesName.join('\n')
145
+ }
146
+ return dependenciesName
147
+ }
148
+
136
149
  return (Function.apply(null, dependenciesName).apply(null, dependencies))
137
150
  }
138
151
 
@@ -189,15 +202,15 @@ function inferTypeByKeyword (schema) {
189
202
  return schema.type
190
203
  }
191
204
 
192
- function hasAnyOf (schema) {
205
+ function hasOf (schema) {
193
206
  if (!schema) { return false }
194
- if ('anyOf' in schema) { return true }
207
+ if ('anyOf' in schema || 'oneOf' in schema) { return true }
195
208
 
196
209
  var objectKeys = Object.keys(schema)
197
210
  for (var i = 0; i < objectKeys.length; i++) {
198
211
  var value = schema[objectKeys[i]]
199
212
  if (typeof value === 'object') {
200
- if (hasAnyOf(value)) { return true }
213
+ if (hasOf(value)) { return true }
201
214
  }
202
215
  }
203
216
 
@@ -209,6 +222,21 @@ function hasIf (schema) {
209
222
  return /"if":{/.test(str) && /"then":{/.test(str)
210
223
  }
211
224
 
225
+ const stringSerializerMap = {
226
+ 'date-time': '$asDatetime',
227
+ date: '$asDate',
228
+ time: '$asTime'
229
+ }
230
+
231
+ function getStringSerializer (format) {
232
+ return stringSerializerMap[format] || '$asString'
233
+ }
234
+
235
+ function $pad2Zeros (num) {
236
+ var s = '00' + num
237
+ return s[s.length - 2] + s[s.length - 1]
238
+ }
239
+
212
240
  function $asNull () {
213
241
  return 'null'
214
242
  }
@@ -248,6 +276,42 @@ function $asBooleanNullable (bool) {
248
276
  return bool === null ? null : $asBoolean(bool)
249
277
  }
250
278
 
279
+ function $asDatetime (date) {
280
+ if (date instanceof Date) {
281
+ return '"' + date.toISOString() + '"'
282
+ } else if (date && typeof date.toISOString === 'function') {
283
+ return '"' + date.toISOString() + '"'
284
+ } else {
285
+ return $asString(date)
286
+ }
287
+ }
288
+
289
+ function $asDate (date) {
290
+ if (date instanceof Date) {
291
+ var year = new Intl.DateTimeFormat('en', { year: 'numeric' }).format(date)
292
+ var month = new Intl.DateTimeFormat('en', { month: '2-digit' }).format(date)
293
+ var day = new Intl.DateTimeFormat('en', { day: '2-digit' }).format(date)
294
+ return '"' + year + '-' + month + '-' + day + '"'
295
+ } else if (date && typeof date.format === 'function') {
296
+ return '"' + date.format('YYYY-MM-DD') + '"'
297
+ } else {
298
+ return $asString(date)
299
+ }
300
+ }
301
+
302
+ function $asTime (date) {
303
+ if (date instanceof Date) {
304
+ var hour = new Intl.DateTimeFormat('en', { hour: 'numeric', hour12: false }).format(date)
305
+ var minute = new Intl.DateTimeFormat('en', { minute: 'numeric' }).format(date)
306
+ var second = new Intl.DateTimeFormat('en', { second: 'numeric' }).format(date)
307
+ return '"' + $pad2Zeros(hour) + ':' + $pad2Zeros(minute) + ':' + $pad2Zeros(second) + '"'
308
+ } else if (date && typeof date.format === 'function') {
309
+ return '"' + date.format('HH:mm:ss') + '"'
310
+ } else {
311
+ return $asString(date)
312
+ }
313
+ }
314
+
251
315
  function $asString (str) {
252
316
  if (str instanceof Date) {
253
317
  return '"' + str.toISOString() + '"'
@@ -317,6 +381,8 @@ function addPatternProperties (schema, externalSchema, fullSchema) {
317
381
  pp[regex] = refFinder(pp[regex].$ref, fullSchema, externalSchema)
318
382
  }
319
383
  var type = pp[regex].type
384
+ var format = pp[regex].format
385
+ var stringSerializer = getStringSerializer(format)
320
386
  code += `
321
387
  if (/${regex.replace(/\\*\//g, '\\/')}/.test(keys[i])) {
322
388
  `
@@ -340,7 +406,7 @@ function addPatternProperties (schema, externalSchema, fullSchema) {
340
406
  } else if (type === 'string') {
341
407
  code += `
342
408
  ${addComma}
343
- json += $asString(keys[i]) + ':' + $asString(obj[keys[i]])
409
+ json += $asString(keys[i]) + ':' + ${stringSerializer}(obj[keys[i]])
344
410
  `
345
411
  } else if (type === 'integer') {
346
412
  code += `
@@ -394,6 +460,8 @@ function additionalProperty (schema, externalSchema, fullSchema) {
394
460
  }
395
461
 
396
462
  var type = ap.type
463
+ var format = ap.format
464
+ var stringSerializer = getStringSerializer(format)
397
465
  if (type === 'object') {
398
466
  code += buildObject(ap, '', 'buildObjectAP', externalSchema)
399
467
  code += `
@@ -414,7 +482,7 @@ function additionalProperty (schema, externalSchema, fullSchema) {
414
482
  } else if (type === 'string') {
415
483
  code += `
416
484
  ${addComma}
417
- json += $asString(keys[i]) + ':' + $asString(obj[keys[i]])
485
+ json += $asString(keys[i]) + ':' + ${stringSerializer}(obj[keys[i]])
418
486
  `
419
487
  } else if (type === 'integer') {
420
488
  code += `
@@ -903,12 +971,12 @@ function buildArrayTypeCondition (type, accessor) {
903
971
  return condition
904
972
  }
905
973
 
906
- function dereferenceAnyOfRefs (schema, externalSchema, fullSchema) {
907
- schema.anyOf.forEach((s, index) => {
974
+ function dereferenceOfRefs (schema, externalSchema, fullSchema, type) {
975
+ schema[type].forEach((s, index) => {
908
976
  // follow the refs
909
977
  while (s.$ref) {
910
- schema.anyOf[index] = refFinder(s.$ref, fullSchema, externalSchema)
911
- s = schema.anyOf[index]
978
+ schema[type][index] = refFinder(s.$ref, fullSchema, externalSchema)
979
+ s = schema[type][index]
912
980
  }
913
981
  })
914
982
  }
@@ -941,7 +1009,8 @@ function nested (laterCode, name, key, schema, externalSchema, fullSchema, subKe
941
1009
  `
942
1010
  break
943
1011
  case 'string':
944
- code += nullable ? `json += obj${accessor} === null ? null : $asString(obj${accessor})` : `json += $asString(obj${accessor})`
1012
+ var stringSerializer = getStringSerializer(schema.format)
1013
+ code += nullable ? `json += obj${accessor} === null ? null : ${stringSerializer}(obj${accessor})` : `json += ${stringSerializer}(obj${accessor})`
945
1014
  break
946
1015
  case 'integer':
947
1016
  code += nullable ? `json += obj${accessor} === null ? null : $asInteger(obj${accessor})` : `json += $asInteger(obj${accessor})`
@@ -968,7 +1037,7 @@ function nested (laterCode, name, key, schema, externalSchema, fullSchema, subKe
968
1037
  break
969
1038
  case undefined:
970
1039
  if ('anyOf' in schema) {
971
- dereferenceAnyOfRefs(schema, externalSchema, fullSchema)
1040
+ dereferenceOfRefs(schema, externalSchema, fullSchema, 'anyOf')
972
1041
  schema.anyOf.forEach((s, index) => {
973
1042
  var nestedResult = nested(laterCode, name, key, s, externalSchema, fullSchema, subKey !== '' ? subKey : 'i' + index)
974
1043
  code += `
@@ -980,6 +1049,19 @@ function nested (laterCode, name, key, schema, externalSchema, fullSchema, subKe
980
1049
  code += `
981
1050
  else json+= null
982
1051
  `
1052
+ } else if ('oneOf' in schema) {
1053
+ dereferenceOfRefs(schema, externalSchema, fullSchema, 'oneOf')
1054
+ schema.oneOf.forEach((s, index) => {
1055
+ var nestedResult = nested(laterCode, name, key, s, externalSchema, fullSchema, subKey !== '' ? subKey : 'i' + index)
1056
+ code += `
1057
+ ${index === 0 ? 'if' : 'else if'}(ajv.validate(${require('util').inspect(s, { depth: null })}, obj${accessor}))
1058
+ ${nestedResult.code}
1059
+ `
1060
+ laterCode = nestedResult.laterCode
1061
+ })
1062
+ code += `
1063
+ else json+= null
1064
+ `
983
1065
  } else if (isEmpty(schema)) {
984
1066
  code += `
985
1067
  json += JSON.stringify(obj${accessor})
@@ -995,9 +1077,10 @@ function nested (laterCode, name, key, schema, externalSchema, fullSchema, subKe
995
1077
  sortedTypes.forEach((type, index) => {
996
1078
  var tempSchema = Object.assign({}, schema, { type })
997
1079
  var nestedResult = nested(laterCode, name, key, tempSchema, externalSchema, fullSchema, subKey)
1080
+
998
1081
  if (type === 'string') {
999
1082
  code += `
1000
- ${index === 0 ? 'if' : 'else if'}(typeof obj${accessor} === "${type}" || obj${accessor} instanceof Date || obj${accessor} instanceof RegExp)
1083
+ ${index === 0 ? 'if' : 'else if'}(obj${accessor} === null || typeof obj${accessor} === "${type}" || obj${accessor} instanceof Date || typeof obj${accessor}.toISOString === "function" || obj${accessor} instanceof RegExp)
1001
1084
  ${nestedResult.code}
1002
1085
  `
1003
1086
  } else if (type === 'null') {
@@ -1082,3 +1165,16 @@ function isEmpty (schema) {
1082
1165
  }
1083
1166
 
1084
1167
  module.exports = build
1168
+
1169
+ module.exports.restore = function (debugModeStr, options = {}) {
1170
+ const dependencies = [debugModeStr]
1171
+ const args = []
1172
+ if (debugModeStr.startsWith('ajv')) {
1173
+ dependencies.unshift('ajv')
1174
+ args.push(new Ajv(options.ajv))
1175
+ }
1176
+
1177
+ // eslint-disable-next-line
1178
+ return (Function.apply(null, ['ajv', debugModeStr])
1179
+ .apply(null, args))
1180
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fast-json-stringify",
3
- "version": "1.17.0",
3
+ "version": "1.20.1",
4
4
  "description": "Stringify your JSON at max speed",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -9,9 +9,7 @@
9
9
  "test:lint": "standard",
10
10
  "test:typescript": "tsc --project ./test/types/tsconfig.json",
11
11
  "test:unit": "tap -J test/*.test.js test/**/*.test.js",
12
- "test": "npm run test:lint && npm run test:unit && npm run test:typescript",
13
- "test:report": "npm run test:unit | tee out.tap",
14
- "test:reporter": "tap-mocha-reporter xunit < out.tap > test/junit-testresults.xml"
12
+ "test": "npm run test:lint && npm run test:unit && npm run test:typescript"
15
13
  },
16
14
  "precommit": "test",
17
15
  "repository": {
@@ -37,12 +35,12 @@
37
35
  "is-my-json-valid": "^2.20.0",
38
36
  "json-strify": "^0.1.7",
39
37
  "long": "^4.0.0",
38
+ "moment": "^2.24.0",
40
39
  "pre-commit": "^1.2.2",
41
40
  "proxyquire": "^2.1.3",
42
41
  "semver": "^7.1.0",
43
42
  "standard": "^14.0.0",
44
43
  "tap": "^12.6.5",
45
- "tap-mocha-reporter": "^3.0.9",
46
44
  "typescript": "^3.7.5",
47
45
  "uglify-es": "^3.3.9"
48
46
  },
@@ -55,10 +53,5 @@
55
53
  "ignore": [
56
54
  "schema-validator.js"
57
55
  ]
58
- },
59
- "greenkeeper": {
60
- "ignore": [
61
- "tap"
62
- ]
63
56
  }
64
57
  }
@@ -1,5 +1,6 @@
1
1
  'use strict'
2
2
 
3
+ const moment = require('moment')
3
4
  const test = require('tap').test
4
5
  const validator = require('is-my-json-valid')
5
6
  const build = require('..')
@@ -164,3 +165,28 @@ test('invalid items throw', (t) => {
164
165
  const stringify = build(schema)
165
166
  t.throws(() => stringify({ args: ['invalid'] }))
166
167
  })
168
+
169
+ test('moment array', (t) => {
170
+ t.plan(1)
171
+ const schema = {
172
+ type: 'object',
173
+ properties: {
174
+ times: {
175
+ type: 'array',
176
+ items: {
177
+ type: 'string',
178
+ format: 'date-time'
179
+ }
180
+ }
181
+ }
182
+ }
183
+ const stringify = build(schema)
184
+ try {
185
+ const value = stringify({
186
+ times: [moment('2018-04-21T07:52:31.017Z')]
187
+ })
188
+ t.is(value, '{"times":["2018-04-21T07:52:31.017Z"]}')
189
+ } catch (e) {
190
+ t.fail(e)
191
+ }
192
+ })