fast-json-stringify 4.0.0 → 5.0.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 +31 -5
- package/ajv.js +37 -0
- package/index.d.ts +25 -1
- package/index.js +260 -586
- package/package.json +4 -8
- package/serializer.js +163 -0
- package/standalone.js +42 -0
- package/test/allof.test.js +9 -7
- package/test/array.test.js +29 -0
- package/test/fixtures/.keep +0 -0
- package/test/ref.test.js +610 -37
- package/test/standalone-mode.test.js +121 -0
- package/test/types/test.ts +9 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fast-json-stringify",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "5.0.0",
|
|
4
4
|
"description": "Stringify your JSON at max speed",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"types": "index.d.ts",
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"benchmark": "^2.1.4",
|
|
38
38
|
"chalk": "^4.1.2",
|
|
39
39
|
"compile-json-stringify": "^0.1.2",
|
|
40
|
-
"inquirer": "^
|
|
40
|
+
"inquirer": "^9.0.0",
|
|
41
41
|
"is-my-json-valid": "^2.20.0",
|
|
42
42
|
"luxon": "^2.4.0",
|
|
43
43
|
"pre-commit": "^1.2.2",
|
|
@@ -53,12 +53,8 @@
|
|
|
53
53
|
"ajv": "^8.10.0",
|
|
54
54
|
"ajv-formats": "^2.1.1",
|
|
55
55
|
"deepmerge": "^4.2.2",
|
|
56
|
-
"fast-uri": "^2.
|
|
57
|
-
"rfdc": "^1.2.0"
|
|
58
|
-
"string-similarity": "^4.0.1"
|
|
59
|
-
},
|
|
60
|
-
"engines": {
|
|
61
|
-
"node": ">= 10.0.0"
|
|
56
|
+
"fast-uri": "^2.1.0",
|
|
57
|
+
"rfdc": "^1.2.0"
|
|
62
58
|
},
|
|
63
59
|
"standard": {
|
|
64
60
|
"ignore": [
|
package/serializer.js
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
module.exports = class Serializer {
|
|
4
|
+
constructor (options = {}) {
|
|
5
|
+
switch (options.rounding) {
|
|
6
|
+
case 'floor':
|
|
7
|
+
this.parseInteger = Math.floor
|
|
8
|
+
break
|
|
9
|
+
case 'ceil':
|
|
10
|
+
this.parseInteger = Math.ceil
|
|
11
|
+
break
|
|
12
|
+
case 'round':
|
|
13
|
+
this.parseInteger = Math.round
|
|
14
|
+
break
|
|
15
|
+
default:
|
|
16
|
+
this.parseInteger = Math.trunc
|
|
17
|
+
break
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
asAny (i) {
|
|
22
|
+
return JSON.stringify(i)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
asNull () {
|
|
26
|
+
return 'null'
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
asInteger (i) {
|
|
30
|
+
if (typeof i === 'bigint') {
|
|
31
|
+
return i.toString()
|
|
32
|
+
} else if (Number.isInteger(i)) {
|
|
33
|
+
return '' + i
|
|
34
|
+
} else {
|
|
35
|
+
/* eslint no-undef: "off" */
|
|
36
|
+
const integer = this.parseInteger(i)
|
|
37
|
+
if (Number.isNaN(integer)) {
|
|
38
|
+
throw new Error(`The value "${i}" cannot be converted to an integer.`)
|
|
39
|
+
} else {
|
|
40
|
+
return '' + integer
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
asIntegerNullable (i) {
|
|
46
|
+
return i === null ? 'null' : this.asInteger(i)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
asNumber (i) {
|
|
50
|
+
const num = Number(i)
|
|
51
|
+
if (Number.isNaN(num)) {
|
|
52
|
+
throw new Error(`The value "${i}" cannot be converted to a number.`)
|
|
53
|
+
} else {
|
|
54
|
+
return '' + num
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
asNumberNullable (i) {
|
|
59
|
+
return i === null ? 'null' : this.asNumber(i)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
asBoolean (bool) {
|
|
63
|
+
return bool && 'true' || 'false' // eslint-disable-line
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
asBooleanNullable (bool) {
|
|
67
|
+
return bool === null ? 'null' : this.asBoolean(bool)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
asDatetime (date) {
|
|
71
|
+
const quotes = '"'
|
|
72
|
+
if (date instanceof Date) {
|
|
73
|
+
return quotes + date.toISOString() + quotes
|
|
74
|
+
}
|
|
75
|
+
return this.asString(date)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
asDatetimeNullable (date) {
|
|
79
|
+
return date === null ? 'null' : this.asDatetime(date)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
asDate (date) {
|
|
83
|
+
const quotes = '"'
|
|
84
|
+
if (date instanceof Date) {
|
|
85
|
+
return quotes + new Date(date.getTime() - (date.getTimezoneOffset() * 60000)).toISOString().slice(0, 10) + quotes
|
|
86
|
+
}
|
|
87
|
+
return this.asString(date)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
asDateNullable (date) {
|
|
91
|
+
return date === null ? 'null' : this.asDate(date)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
asTime (date) {
|
|
95
|
+
const quotes = '"'
|
|
96
|
+
if (date instanceof Date) {
|
|
97
|
+
return quotes + new Date(date.getTime() - (date.getTimezoneOffset() * 60000)).toISOString().slice(11, 19) + quotes
|
|
98
|
+
}
|
|
99
|
+
return this.asString(date)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
asTimeNullable (date) {
|
|
103
|
+
return date === null ? 'null' : this.asTime(date)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
asString (str) {
|
|
107
|
+
const quotes = '"'
|
|
108
|
+
if (str instanceof Date) {
|
|
109
|
+
return quotes + str.toISOString() + quotes
|
|
110
|
+
} else if (str === null) {
|
|
111
|
+
return quotes + quotes
|
|
112
|
+
} else if (str instanceof RegExp) {
|
|
113
|
+
str = str.source
|
|
114
|
+
} else if (typeof str !== 'string') {
|
|
115
|
+
str = str.toString()
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (str.length < 42) {
|
|
119
|
+
return this.asStringSmall(str)
|
|
120
|
+
} else {
|
|
121
|
+
return JSON.stringify(str)
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
asStringNullable (str) {
|
|
126
|
+
return str === null ? 'null' : this.asString(str)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// magically escape strings for json
|
|
130
|
+
// relying on their charCodeAt
|
|
131
|
+
// everything below 32 needs JSON.stringify()
|
|
132
|
+
// every string that contain surrogate needs JSON.stringify()
|
|
133
|
+
// 34 and 92 happens all the time, so we
|
|
134
|
+
// have a fast case for them
|
|
135
|
+
asStringSmall (str) {
|
|
136
|
+
const l = str.length
|
|
137
|
+
let result = ''
|
|
138
|
+
let last = 0
|
|
139
|
+
let found = false
|
|
140
|
+
let surrogateFound = false
|
|
141
|
+
let point = 255
|
|
142
|
+
// eslint-disable-next-line
|
|
143
|
+
for (var i = 0; i < l && point >= 32; i++) {
|
|
144
|
+
point = str.charCodeAt(i)
|
|
145
|
+
if (point >= 0xD800 && point <= 0xDFFF) {
|
|
146
|
+
// The current character is a surrogate.
|
|
147
|
+
surrogateFound = true
|
|
148
|
+
}
|
|
149
|
+
if (point === 34 || point === 92) {
|
|
150
|
+
result += str.slice(last, i) + '\\'
|
|
151
|
+
last = i
|
|
152
|
+
found = true
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (!found) {
|
|
157
|
+
result = str
|
|
158
|
+
} else {
|
|
159
|
+
result += str.slice(last)
|
|
160
|
+
}
|
|
161
|
+
return ((point < 32) || (surrogateFound === true)) ? JSON.stringify(str) : '"' + result + '"'
|
|
162
|
+
}
|
|
163
|
+
}
|
package/standalone.js
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
const fs = require('fs')
|
|
2
|
+
const path = require('path')
|
|
3
|
+
|
|
4
|
+
function buildStandaloneCode (options, ajvInstance, contextFunctionCode) {
|
|
5
|
+
const serializerCode = fs.readFileSync(path.join(__dirname, 'serializer.js')).toString()
|
|
6
|
+
let buildAjvCode = ''
|
|
7
|
+
let defaultAjvSchema = ''
|
|
8
|
+
const defaultMeta = ajvInstance.defaultMeta()
|
|
9
|
+
if (typeof defaultMeta === 'string') {
|
|
10
|
+
defaultAjvSchema = defaultMeta
|
|
11
|
+
} else {
|
|
12
|
+
defaultAjvSchema = defaultMeta.$id || defaultMeta.id
|
|
13
|
+
}
|
|
14
|
+
const shouldUseAjv = contextFunctionCode.indexOf('ajv') !== -1
|
|
15
|
+
// we need to export the custom json schema
|
|
16
|
+
let ajvSchemasCode = ''
|
|
17
|
+
if (shouldUseAjv) {
|
|
18
|
+
ajvSchemasCode += `const ajv = buildAjv(${JSON.stringify(options.ajv || {})})\n`
|
|
19
|
+
for (const [id, schema] of Object.entries(ajvInstance.schemas)) {
|
|
20
|
+
// should skip ajv default schema
|
|
21
|
+
if (id === defaultAjvSchema) continue
|
|
22
|
+
ajvSchemasCode += `ajv.addSchema(${JSON.stringify(schema.schema)}, "${id}")\n`
|
|
23
|
+
}
|
|
24
|
+
buildAjvCode = fs.readFileSync(path.join(__dirname, 'ajv.js')).toString()
|
|
25
|
+
buildAjvCode = buildAjvCode.replace("'use strict'", '').replace('module.exports = buildAjv', '')
|
|
26
|
+
}
|
|
27
|
+
return `
|
|
28
|
+
'use strict'
|
|
29
|
+
|
|
30
|
+
${serializerCode.replace("'use strict'", '').replace('module.exports = ', '')}
|
|
31
|
+
${buildAjvCode}
|
|
32
|
+
|
|
33
|
+
const serializer = new Serializer(${JSON.stringify(options || {})})
|
|
34
|
+
${ajvSchemasCode}
|
|
35
|
+
|
|
36
|
+
${contextFunctionCode.replace('return main', '')}
|
|
37
|
+
|
|
38
|
+
module.exports = main
|
|
39
|
+
`
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
module.exports = buildStandaloneCode
|
package/test/allof.test.js
CHANGED
|
@@ -402,12 +402,14 @@ test('object with external $refs in allOf', (t) => {
|
|
|
402
402
|
}
|
|
403
403
|
},
|
|
404
404
|
second: {
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
405
|
+
definitions: {
|
|
406
|
+
id2: {
|
|
407
|
+
$id: '#id2',
|
|
408
|
+
type: 'object',
|
|
409
|
+
properties: {
|
|
410
|
+
id2: {
|
|
411
|
+
type: 'integer'
|
|
412
|
+
}
|
|
411
413
|
}
|
|
412
414
|
}
|
|
413
415
|
}
|
|
@@ -422,7 +424,7 @@ test('object with external $refs in allOf', (t) => {
|
|
|
422
424
|
$ref: 'first#/definitions/id1'
|
|
423
425
|
},
|
|
424
426
|
{
|
|
425
|
-
$ref: 'second
|
|
427
|
+
$ref: 'second#/definitions/id2'
|
|
426
428
|
}
|
|
427
429
|
]
|
|
428
430
|
}
|
package/test/array.test.js
CHANGED
|
@@ -218,6 +218,35 @@ test('array items is a list of schema and additionalItems is true, just the desc
|
|
|
218
218
|
t.equal(result, '{"foo":["foo","bar",1]}')
|
|
219
219
|
})
|
|
220
220
|
|
|
221
|
+
test('array items is a list of schema and additionalItems is true, just the described item is validated', (t) => {
|
|
222
|
+
t.plan(1)
|
|
223
|
+
|
|
224
|
+
const schema = {
|
|
225
|
+
type: 'object',
|
|
226
|
+
properties: {
|
|
227
|
+
foo: {
|
|
228
|
+
type: 'array',
|
|
229
|
+
items: [
|
|
230
|
+
{
|
|
231
|
+
type: 'string'
|
|
232
|
+
},
|
|
233
|
+
{
|
|
234
|
+
type: 'number'
|
|
235
|
+
}
|
|
236
|
+
],
|
|
237
|
+
additionalItems: true
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const stringify = build(schema)
|
|
243
|
+
const result = stringify({
|
|
244
|
+
foo: ['foo']
|
|
245
|
+
})
|
|
246
|
+
|
|
247
|
+
t.equal(result, '{"foo":["foo"]}')
|
|
248
|
+
})
|
|
249
|
+
|
|
221
250
|
test('array items is a list of schema and additionalItems is false', (t) => {
|
|
222
251
|
t.plan(1)
|
|
223
252
|
|
|
File without changes
|