Skip to content

Commit 455bc28

Browse files
authored
Merge pull request #38 from fastify/fix-array
Avoid calling substring to remove a comma.
2 parents be5e563 + 8b3b18b commit 455bc28

File tree

5 files changed

+93
-72
lines changed

5 files changed

+93
-72
lines changed

.travis.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,5 @@ node_js:
44
- '4'
55
- '5'
66
- '6'
7+
- '7'
8+
- '8'

README.md

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,20 +7,22 @@ advantages reduces on large payloads.
77
Benchmarks:
88

99
```
10-
JSON.stringify array x 3,288 ops/sec ±5.18% (82 runs sampled)
11-
fast-json-stringify array x 1,813 ops/sec ±10.21% (71 runs sampled)
12-
fast-json-stringify-uglified array x 2,106 ops/sec ±3.23% (83 runs sampled)
13-
JSON.stringify long string x 12,933 ops/sec ±1.27% (87 runs sampled)
14-
fast-json-stringify long string x 12,221 ops/sec ±3.31% (84 runs sampled)
15-
fast-json-stringify-uglified long string x 13,256 ops/sec ±0.95% (92 runs sampled)
16-
JSON.stringify short string x 4,878,641 ops/sec ±1.14% (90 runs sampled)
17-
fast-json-stringify short string x 11,649,100 ops/sec ±0.98% (91 runs sampled)
18-
fast-json-stringify-uglified short string x 11,877,661 ops/sec ±0.91% (90 runs sampled)
19-
JSON.stringify obj x 1,705,377 ops/sec ±2.61% (87 runs sampled)
20-
fast-json-stringify obj x 2,268,915 ops/sec ±1.39% (90 runs sampled)
21-
fast-json-stringify-uglified obj x 2,243,341 ops/sec ±1.11% (89 runs sampled)
10+
JSON.stringify array x 3,343 ops/sec ±1.28% (86 runs sampled)
11+
fast-json-stringify array x 3,031 ops/sec ±1.38% (88 runs sampled)
12+
fast-json-stringify-uglified array x 3,222 ops/sec ±1.12% (87 runs sampled)
13+
JSON.stringify long string x 12,400 ops/sec ±1.25% (88 runs sampled)
14+
fast-json-stringify long string x 12,151 ops/sec ±1.16% (84 runs sampled)
15+
fast-json-stringify-uglified long string x 12,304 ops/sec ±1.00% (87 runs sampled)
16+
JSON.stringify short string x 4,384,078 ops/sec ±1.53% (88 runs sampled)
17+
fast-json-stringify short string x 11,062,569 ops/sec ±1.10% (85 runs sampled)
18+
fast-json-stringify-uglified short string x 10,642,943 ops/sec ±3.01% (86 runs sampled)
19+
JSON.stringify obj x 1,612,312 ops/sec ±1.40% (87 runs sampled)
20+
fast-json-stringify obj x 3,149,885 ops/sec ±1.46% (85 runs sampled)
21+
fast-json-stringify-uglified obj x 3,374,838 ops/sec ±1.35% (87 runs sampled)
2222
```
2323

24+
Benchmarks taken on Node 6.11.0.
25+
2426
#### Table of contents:
2527
- <a href="#example">`Example`</a>
2628
- <a href="#api">`API`</a>

index.js

Lines changed: 62 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,22 @@
11
'use strict'
22

3-
const fastSafeStringify = require('fast-safe-stringify')
3+
var fastSafeStringify = require('fast-safe-stringify')
44

55
var uglify = null
6-
let isLong
6+
var isLong
77
try {
88
isLong = require('long').isLong
99
} catch (e) {
1010
isLong = null
1111
}
1212

13+
var addComma = `
14+
if (addComma) {
15+
json += ','
16+
}
17+
addComma = true
18+
`
19+
1320
function build (schema, options) {
1421
options = options || {}
1522
/* eslint no-new-func: "off" */
@@ -19,7 +26,7 @@ function build (schema, options) {
1926
// used to support patternProperties and additionalProperties
2027
// they need to check if a field belongs to the properties in the schema
2128
code += `
22-
const properties = ${JSON.stringify(schema.properties)} || {}
29+
var properties = ${JSON.stringify(schema.properties)} || {}
2330
`
2431
code += `
2532
${$asString.toString()}
@@ -32,12 +39,12 @@ function build (schema, options) {
3239
// only handle longs if the module is used
3340
if (isLong) {
3441
code += `
35-
const isLong = ${isLong.toString()}
42+
var isLong = ${isLong.toString()}
3643
${$asInteger.toString()}
3744
`
3845
} else {
3946
code += `
40-
const $asInteger = $asNumber
47+
var $asInteger = $asNumber
4148
`
4249
}
4350

@@ -168,7 +175,7 @@ function $asStringSmall (str) {
168175

169176
function addPatternProperties (schema, externalSchema, fullSchema) {
170177
var pp = schema.patternProperties
171-
let code = `
178+
var code = `
172179
var keys = Object.keys(obj)
173180
for (var i = 0; i < keys.length; i++) {
174181
if (properties[keys[i]]) continue
@@ -184,32 +191,39 @@ function addPatternProperties (schema, externalSchema, fullSchema) {
184191
if (type === 'object') {
185192
code += buildObject(pp[regex], '', 'buildObjectPP' + index, externalSchema, fullSchema)
186193
code += `
187-
json += $asString(keys[i]) + ':' + buildObjectPP${index}(obj[keys[i]]) + ','
194+
${addComma}
195+
json += $asString(keys[i]) + ':' + buildObjectPP${index}(obj[keys[i]])
188196
`
189197
} else if (type === 'array') {
190198
code += buildArray(pp[regex], '', 'buildArrayPP' + index, externalSchema, fullSchema)
191199
code += `
192-
json += $asString(keys[i]) + ':' + buildArrayPP${index}(obj[keys[i]]) + ','
200+
${addComma}
201+
json += $asString(keys[i]) + ':' + buildArrayPP${index}(obj[keys[i]])
193202
`
194203
} else if (type === 'null') {
195204
code += `
196-
json += $asString(keys[i]) +':null,'
205+
${addComma}
206+
json += $asString(keys[i]) +':null'
197207
`
198208
} else if (type === 'string') {
199209
code += `
200-
json += $asString(keys[i]) + ':' + $asString(obj[keys[i]]) + ','
210+
${addComma}
211+
json += $asString(keys[i]) + ':' + $asString(obj[keys[i]])
201212
`
202213
} else if (type === 'integer') {
203214
code += `
204-
json += $asString(keys[i]) + ':' + $asInteger(obj[keys[i]]) + ','
215+
${addComma}
216+
json += $asString(keys[i]) + ':' + $asInteger(obj[keys[i]])
205217
`
206218
} else if (type === 'number') {
207219
code += `
208-
json += $asString(keys[i]) + ':' + $asNumber(obj[keys[i]]) + ','
220+
${addComma}
221+
json += $asString(keys[i]) + ':' + $asNumber(obj[keys[i]])
209222
`
210223
} else if (type === 'boolean') {
211224
code += `
212-
json += $asString(keys[i]) + ':' + $asBoolean(obj[keys[i]]) + ','
225+
${addComma}
226+
json += $asString(keys[i]) + ':' + $asBoolean(obj[keys[i]])
213227
`
214228
} else {
215229
code += `
@@ -234,47 +248,56 @@ function addPatternProperties (schema, externalSchema, fullSchema) {
234248

235249
function additionalProperty (schema, externalSchema, fullSchema) {
236250
var ap = schema.additionalProperties
237-
let code = ''
251+
var code = ''
238252
if (ap === true) {
239253
return `
240-
if (obj[keys[i]] !== undefined)
241-
json += $asString(keys[i]) + ':' + fastSafeStringify(obj[keys[i]]) + ','
254+
if (obj[keys[i]] !== undefined) {
255+
${addComma}
256+
json += $asString(keys[i]) + ':' + fastSafeStringify(obj[keys[i]])
257+
}
242258
`
243259
}
244260
if (ap['$ref']) {
245261
ap = refFinder(ap['$ref'], fullSchema, externalSchema)
246262
}
247263

248-
let type = ap.type
264+
var type = ap.type
249265
if (type === 'object') {
250266
code += buildObject(ap, '', 'buildObjectAP', externalSchema)
251267
code += `
252-
json += $asString(keys[i]) + ':' + buildObjectAP(obj[keys[i]]) + ','
268+
${addComma}
269+
json += $asString(keys[i]) + ':' + buildObjectAP(obj[keys[i]])
253270
`
254271
} else if (type === 'array') {
255272
code += buildArray(ap, '', 'buildArrayAP', externalSchema, fullSchema)
256273
code += `
257-
json += $asString(keys[i]) + ':' + buildArrayAP(obj[keys[i]]) + ','
274+
${addComma}
275+
json += $asString(keys[i]) + ':' + buildArrayAP(obj[keys[i]])
258276
`
259277
} else if (type === 'null') {
260278
code += `
261-
json += $asString(keys[i]) +':null,'
279+
${addComma}
280+
json += $asString(keys[i]) +':null'
262281
`
263282
} else if (type === 'string') {
264283
code += `
265-
json += $asString(keys[i]) + ':' + $asString(obj[keys[i]]) + ','
284+
${addComma}
285+
json += $asString(keys[i]) + ':' + $asString(obj[keys[i]])
266286
`
267287
} else if (type === 'integer') {
268288
code += `
269-
json += $asString(keys[i]) + ':' + $asInteger(obj[keys[i]]) + ','
289+
${addComma}
290+
json += $asString(keys[i]) + ':' + $asInteger(obj[keys[i]])
270291
`
271292
} else if (type === 'number') {
272293
code += `
273-
json += $asString(keys[i]) + ':' + $asNumber(obj[keys[i]]) + ','
294+
${addComma}
295+
json += $asString(keys[i]) + ':' + $asNumber(obj[keys[i]])
274296
`
275297
} else if (type === 'boolean') {
276298
code += `
277-
json += $asString(keys[i]) + ':' + $asBoolean(obj[keys[i]]) + ','
299+
${addComma}
300+
json += $asString(keys[i]) + ':' + $asBoolean(obj[keys[i]])
278301
`
279302
} else {
280303
code += `
@@ -301,9 +324,9 @@ function refFinder (ref, schema, externalSchema) {
301324
if (ref[0]) {
302325
schema = externalSchema[ref[0]]
303326
}
304-
const walk = ref[1].split('/')
305-
let code = 'return schema'
306-
for (let i = 1; i < walk.length; i++) {
327+
var walk = ref[1].split('/')
328+
var code = 'return schema'
329+
for (var i = 1; i < walk.length; i++) {
307330
code += `['${walk[i]}']`
308331
}
309332
return (new Function('schema', code))(schema)
@@ -313,6 +336,7 @@ function buildObject (schema, code, name, externalSchema, fullSchema) {
313336
code += `
314337
function ${name} (obj) {
315338
var json = '{'
339+
var addComma = false
316340
`
317341

318342
if (schema.patternProperties) {
@@ -328,24 +352,18 @@ function buildObject (schema, code, name, externalSchema, fullSchema) {
328352
// see https://github.com/mcollina/fast-json-stringify/pull/3 for discussion.
329353
code += `
330354
if (obj['${key}'] !== undefined) {
355+
${addComma}
331356
json += '${$asString(key)}:'
332357
`
333358

334359
if (schema.properties[key]['$ref']) {
335360
schema.properties[key] = refFinder(schema.properties[key]['$ref'], fullSchema, externalSchema)
336361
}
337362

338-
const result = nested(laterCode, name, key, schema.properties[key], externalSchema, fullSchema)
363+
var result = nested(laterCode, name, key, schema.properties[key], externalSchema, fullSchema)
339364

340365
code += result.code
341366
laterCode = result.laterCode
342-
/* eslint-disable no-useless-escape */
343-
if (i < a.length - 1) {
344-
code += `
345-
json += \',\'
346-
`
347-
}
348-
/* eslint-enable no-useless-escape */
349367

350368
if (schema.required && schema.required.indexOf(key) !== -1) {
351369
code += `
@@ -361,7 +379,6 @@ function buildObject (schema, code, name, externalSchema, fullSchema) {
361379

362380
// Removes the comma if is the last element of the string (in case there are not properties)
363381
code += `
364-
if (json[json.length - 1] === ',') json = json.substring(0, json.length - 1)
365382
json += '}'
366383
return json
367384
}
@@ -383,16 +400,16 @@ function buildArray (schema, code, name, externalSchema, fullSchema) {
383400
schema.items = refFinder(schema.items['$ref'], fullSchema, externalSchema)
384401
}
385402

386-
const result = nested(laterCode, name, '[i]', schema.items, externalSchema, fullSchema)
403+
var result = nested(laterCode, name, '[i]', schema.items, externalSchema, fullSchema)
387404

388405
code += `
389-
const l = obj.length
390-
const w = l - 1
406+
var l = obj.length
407+
var w = l - 1
391408
for (var i = 0; i < l; i++) {
392-
${result.code}
393-
if (i < w) {
409+
if (i > 0) {
394410
json += ','
395411
}
412+
${result.code}
396413
}
397414
`
398415

@@ -412,8 +429,8 @@ function buildArray (schema, code, name, externalSchema, fullSchema) {
412429
function nested (laterCode, name, key, schema, externalSchema, fullSchema) {
413430
var code = ''
414431
var funcName
415-
const type = schema.type
416-
const accessor = key.indexOf('[') === 0 ? key : `['${key}']`
432+
var type = schema.type
433+
var accessor = key.indexOf('[') === 0 ? key : `['${key}']`
417434
switch (type) {
418435
case 'null':
419436
code += `
@@ -469,7 +486,7 @@ function uglifyCode (code) {
469486
loadUglify()
470487
}
471488

472-
const uglified = uglify.minify(code, { parse: { bare_returns: true } })
489+
var uglified = uglify.minify(code, { parse: { bare_returns: true } })
473490

474491
if (uglified.error) {
475492
throw uglified.error
@@ -481,7 +498,7 @@ function uglifyCode (code) {
481498
function loadUglify () {
482499
try {
483500
uglify = require('uglify-es')
484-
const uglifyVersion = require('uglify-es/package.json').version
501+
var uglifyVersion = require('uglify-es/package.json').version
485502

486503
if (uglifyVersion[0] !== '3') {
487504
throw new Error('Only version 3 of uglify-es is supported')

test/patternProperties.test.js

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ test('patternProperties', (t) => {
2121
})
2222

2323
let obj = { str: 'test', foo: 42, ofoo: true, foof: 'string', objfoo: {a: true}, notMe: false }
24-
t.equal('{"foo":"42","ofoo":"true","foof":"string","objfoo":"[object Object]","str":"test"}', stringify(obj))
24+
t.equal(stringify(obj), '{"foo":"42","ofoo":"true","foof":"string","objfoo":"[object Object]","str":"test"}')
2525
})
2626

2727
test('patternProperties should not change properties', (t) => {
@@ -42,7 +42,7 @@ test('patternProperties should not change properties', (t) => {
4242
})
4343

4444
const obj = { foo: '42', ofoo: 42 }
45-
t.equal('{"ofoo":42,"foo":"42"}', stringify(obj))
45+
t.equal(stringify(obj), '{"ofoo":42,"foo":"42"}')
4646
})
4747

4848
test('patternProperties - string coerce', (t) => {
@@ -59,7 +59,7 @@ test('patternProperties - string coerce', (t) => {
5959
})
6060

6161
const obj = { foo: true, ofoo: 42, arrfoo: ['array', 'test'], objfoo: { a: 'world' } }
62-
t.equal('{"foo":"true","ofoo":"42","arrfoo":"array,test","objfoo":"[object Object]"}', stringify(obj))
62+
t.equal(stringify(obj), '{"foo":"true","ofoo":"42","arrfoo":"array,test","objfoo":"[object Object]"}')
6363
})
6464

6565
test('patternProperties - number coerce', (t) => {
@@ -76,7 +76,7 @@ test('patternProperties - number coerce', (t) => {
7676
})
7777

7878
const obj = { foo: true, ofoo: '42', xfoo: 'string', arrfoo: [1, 2], objfoo: { num: 42 } }
79-
t.equal('{"foo":1,"ofoo":42,"xfoo":null,"arrfoo":null,"objfoo":null}', stringify(obj))
79+
t.equal(stringify(obj), '{"foo":1,"ofoo":42,"xfoo":null,"arrfoo":null,"objfoo":null}')
8080
})
8181

8282
test('patternProperties - boolean coerce', (t) => {
@@ -93,7 +93,7 @@ test('patternProperties - boolean coerce', (t) => {
9393
})
9494

9595
const obj = { foo: 'true', ofoo: 0, arrfoo: [1, 2], objfoo: { a: true } }
96-
t.equal('{"foo":true,"ofoo":false,"arrfoo":true,"objfoo":true}', stringify(obj))
96+
t.equal(stringify(obj), '{"foo":true,"ofoo":false,"arrfoo":true,"objfoo":true}')
9797
})
9898

9999
test('patternProperties - object coerce', (t) => {
@@ -115,7 +115,7 @@ test('patternProperties - object coerce', (t) => {
115115
})
116116

117117
const obj = { objfoo: { answer: 42 } }
118-
t.equal('{"objfoo":{"answer":42}}', stringify(obj))
118+
t.equal(stringify(obj), '{"objfoo":{"answer":42}}')
119119
})
120120

121121
test('patternProperties - array coerce', (t) => {
@@ -135,7 +135,7 @@ test('patternProperties - array coerce', (t) => {
135135
})
136136

137137
const obj = { foo: 'true', ofoo: 0, arrfoo: [1, 2], objfoo: { tyrion: 'lannister' } }
138-
t.equal('{"foo":["t","r","u","e"],"ofoo":[],"arrfoo":["1","2"],"objfoo":[]}', stringify(obj))
138+
t.equal(stringify(obj), '{"foo":["t","r","u","e"],"ofoo":[],"arrfoo":["1","2"],"objfoo":[]}')
139139
})
140140

141141
test('patternProperties - throw on unknown type', (t) => {

0 commit comments

Comments
 (0)