Skip to content

Avoid calling substring to remove a comma. #38

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jul 5, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,5 @@ node_js:
- '4'
- '5'
- '6'
- '7'
- '8'
26 changes: 14 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,22 @@ advantages reduces on large payloads.
Benchmarks:

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

Benchmarks taken on Node 6.11.0.

#### Table of contents:
- <a href="#example">`Example`</a>
- <a href="#api">`API`</a>
Expand Down
107 changes: 62 additions & 45 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,22 @@
'use strict'

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

var uglify = null
let isLong
var isLong
try {
isLong = require('long').isLong
} catch (e) {
isLong = null
}

var addComma = `
if (addComma) {
json += ','
}
addComma = true
`

function build (schema, options) {
options = options || {}
/* eslint no-new-func: "off" */
Expand All @@ -19,7 +26,7 @@ function build (schema, options) {
// used to support patternProperties and additionalProperties
// they need to check if a field belongs to the properties in the schema
code += `
const properties = ${JSON.stringify(schema.properties)} || {}
var properties = ${JSON.stringify(schema.properties)} || {}
`
code += `
${$asString.toString()}
Expand All @@ -32,12 +39,12 @@ function build (schema, options) {
// only handle longs if the module is used
if (isLong) {
code += `
const isLong = ${isLong.toString()}
var isLong = ${isLong.toString()}
${$asInteger.toString()}
`
} else {
code += `
const $asInteger = $asNumber
var $asInteger = $asNumber
`
}

Expand Down Expand Up @@ -168,7 +175,7 @@ function $asStringSmall (str) {

function addPatternProperties (schema, externalSchema, fullSchema) {
var pp = schema.patternProperties
let code = `
var code = `
var keys = Object.keys(obj)
for (var i = 0; i < keys.length; i++) {
if (properties[keys[i]]) continue
Expand All @@ -184,32 +191,39 @@ function addPatternProperties (schema, externalSchema, fullSchema) {
if (type === 'object') {
code += buildObject(pp[regex], '', 'buildObjectPP' + index, externalSchema, fullSchema)
code += `
json += $asString(keys[i]) + ':' + buildObjectPP${index}(obj[keys[i]]) + ','
${addComma}
json += $asString(keys[i]) + ':' + buildObjectPP${index}(obj[keys[i]])
`
} else if (type === 'array') {
code += buildArray(pp[regex], '', 'buildArrayPP' + index, externalSchema, fullSchema)
code += `
json += $asString(keys[i]) + ':' + buildArrayPP${index}(obj[keys[i]]) + ','
${addComma}
json += $asString(keys[i]) + ':' + buildArrayPP${index}(obj[keys[i]])
`
} else if (type === 'null') {
code += `
json += $asString(keys[i]) +':null,'
${addComma}
json += $asString(keys[i]) +':null'
`
} else if (type === 'string') {
code += `
json += $asString(keys[i]) + ':' + $asString(obj[keys[i]]) + ','
${addComma}
json += $asString(keys[i]) + ':' + $asString(obj[keys[i]])
`
} else if (type === 'integer') {
code += `
json += $asString(keys[i]) + ':' + $asInteger(obj[keys[i]]) + ','
${addComma}
json += $asString(keys[i]) + ':' + $asInteger(obj[keys[i]])
`
} else if (type === 'number') {
code += `
json += $asString(keys[i]) + ':' + $asNumber(obj[keys[i]]) + ','
${addComma}
json += $asString(keys[i]) + ':' + $asNumber(obj[keys[i]])
`
} else if (type === 'boolean') {
code += `
json += $asString(keys[i]) + ':' + $asBoolean(obj[keys[i]]) + ','
${addComma}
json += $asString(keys[i]) + ':' + $asBoolean(obj[keys[i]])
`
} else {
code += `
Expand All @@ -234,47 +248,56 @@ function addPatternProperties (schema, externalSchema, fullSchema) {

function additionalProperty (schema, externalSchema, fullSchema) {
var ap = schema.additionalProperties
let code = ''
var code = ''
if (ap === true) {
return `
if (obj[keys[i]] !== undefined)
json += $asString(keys[i]) + ':' + fastSafeStringify(obj[keys[i]]) + ','
if (obj[keys[i]] !== undefined) {
${addComma}
json += $asString(keys[i]) + ':' + fastSafeStringify(obj[keys[i]])
}
`
}
if (ap['$ref']) {
ap = refFinder(ap['$ref'], fullSchema, externalSchema)
}

let type = ap.type
var type = ap.type
if (type === 'object') {
code += buildObject(ap, '', 'buildObjectAP', externalSchema)
code += `
json += $asString(keys[i]) + ':' + buildObjectAP(obj[keys[i]]) + ','
${addComma}
json += $asString(keys[i]) + ':' + buildObjectAP(obj[keys[i]])
`
} else if (type === 'array') {
code += buildArray(ap, '', 'buildArrayAP', externalSchema, fullSchema)
code += `
json += $asString(keys[i]) + ':' + buildArrayAP(obj[keys[i]]) + ','
${addComma}
json += $asString(keys[i]) + ':' + buildArrayAP(obj[keys[i]])
`
} else if (type === 'null') {
code += `
json += $asString(keys[i]) +':null,'
${addComma}
json += $asString(keys[i]) +':null'
`
} else if (type === 'string') {
code += `
json += $asString(keys[i]) + ':' + $asString(obj[keys[i]]) + ','
${addComma}
json += $asString(keys[i]) + ':' + $asString(obj[keys[i]])
`
} else if (type === 'integer') {
code += `
json += $asString(keys[i]) + ':' + $asInteger(obj[keys[i]]) + ','
${addComma}
json += $asString(keys[i]) + ':' + $asInteger(obj[keys[i]])
`
} else if (type === 'number') {
code += `
json += $asString(keys[i]) + ':' + $asNumber(obj[keys[i]]) + ','
${addComma}
json += $asString(keys[i]) + ':' + $asNumber(obj[keys[i]])
`
} else if (type === 'boolean') {
code += `
json += $asString(keys[i]) + ':' + $asBoolean(obj[keys[i]]) + ','
${addComma}
json += $asString(keys[i]) + ':' + $asBoolean(obj[keys[i]])
`
} else {
code += `
Expand All @@ -301,9 +324,9 @@ function refFinder (ref, schema, externalSchema) {
if (ref[0]) {
schema = externalSchema[ref[0]]
}
const walk = ref[1].split('/')
let code = 'return schema'
for (let i = 1; i < walk.length; i++) {
var walk = ref[1].split('/')
var code = 'return schema'
for (var i = 1; i < walk.length; i++) {
code += `['${walk[i]}']`
}
return (new Function('schema', code))(schema)
Expand All @@ -313,6 +336,7 @@ function buildObject (schema, code, name, externalSchema, fullSchema) {
code += `
function ${name} (obj) {
var json = '{'
var addComma = false
`

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

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

const result = nested(laterCode, name, key, schema.properties[key], externalSchema, fullSchema)
var result = nested(laterCode, name, key, schema.properties[key], externalSchema, fullSchema)

code += result.code
laterCode = result.laterCode
/* eslint-disable no-useless-escape */
if (i < a.length - 1) {
code += `
json += \',\'
`
}
/* eslint-enable no-useless-escape */

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

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

const result = nested(laterCode, name, '[i]', schema.items, externalSchema, fullSchema)
var result = nested(laterCode, name, '[i]', schema.items, externalSchema, fullSchema)

code += `
const l = obj.length
const w = l - 1
var l = obj.length
var w = l - 1
for (var i = 0; i < l; i++) {
${result.code}
if (i < w) {
if (i > 0) {
json += ','
}
${result.code}
}
`

Expand All @@ -412,8 +429,8 @@ function buildArray (schema, code, name, externalSchema, fullSchema) {
function nested (laterCode, name, key, schema, externalSchema, fullSchema) {
var code = ''
var funcName
const type = schema.type
const accessor = key.indexOf('[') === 0 ? key : `['${key}']`
var type = schema.type
var accessor = key.indexOf('[') === 0 ? key : `['${key}']`
switch (type) {
case 'null':
code += `
Expand Down Expand Up @@ -469,7 +486,7 @@ function uglifyCode (code) {
loadUglify()
}

const uglified = uglify.minify(code, { parse: { bare_returns: true } })
var uglified = uglify.minify(code, { parse: { bare_returns: true } })

if (uglified.error) {
throw uglified.error
Expand All @@ -481,7 +498,7 @@ function uglifyCode (code) {
function loadUglify () {
try {
uglify = require('uglify-es')
const uglifyVersion = require('uglify-es/package.json').version
var uglifyVersion = require('uglify-es/package.json').version

if (uglifyVersion[0] !== '3') {
throw new Error('Only version 3 of uglify-es is supported')
Expand Down
14 changes: 7 additions & 7 deletions test/patternProperties.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ test('patternProperties', (t) => {
})

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

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

const obj = { foo: '42', ofoo: 42 }
t.equal('{"ofoo":42,"foo":"42"}', stringify(obj))
t.equal(stringify(obj), '{"ofoo":42,"foo":"42"}')
})

test('patternProperties - string coerce', (t) => {
Expand All @@ -59,7 +59,7 @@ test('patternProperties - string coerce', (t) => {
})

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

test('patternProperties - number coerce', (t) => {
Expand All @@ -76,7 +76,7 @@ test('patternProperties - number coerce', (t) => {
})

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

test('patternProperties - boolean coerce', (t) => {
Expand All @@ -93,7 +93,7 @@ test('patternProperties - boolean coerce', (t) => {
})

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

test('patternProperties - object coerce', (t) => {
Expand All @@ -115,7 +115,7 @@ test('patternProperties - object coerce', (t) => {
})

const obj = { objfoo: { answer: 42 } }
t.equal('{"objfoo":{"answer":42}}', stringify(obj))
t.equal(stringify(obj), '{"objfoo":{"answer":42}}')
})

test('patternProperties - array coerce', (t) => {
Expand All @@ -135,7 +135,7 @@ test('patternProperties - array coerce', (t) => {
})

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

test('patternProperties - throw on unknown type', (t) => {
Expand Down
Loading