Skip to content

Commit

Permalink
perf: improve performance to escape big strings
Browse files Browse the repository at this point in the history
The time to escape strings that contained characters that needed
escaping is now lower than before. Native JSON.stringify() got a
lot faster in newer versions and may now be used directly.

This has to side effect to also reduce the code size a bit.
  • Loading branch information
BridgeAR committed Mar 19, 2023
1 parent 5c078f2 commit dbc5143
Show file tree
Hide file tree
Showing 2 changed files with 11 additions and 62 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
## v2.4.3

- Fixed replacer function receiving array keys as number instead of string
- Improved performance to escape long strings that contain characters that need escaping

## v2.4.2

Expand Down
72 changes: 10 additions & 62 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,67 +21,15 @@ module.exports = stringify

// eslint-disable-next-line no-control-regex
const strEscapeSequencesRegExp = /[\u0000-\u001f\u0022\u005c\ud800-\udfff]|[\ud800-\udbff](?![\udc00-\udfff])|(?:[^\ud800-\udbff]|^)[\udc00-\udfff]/
const strEscapeSequencesReplacer = new RegExp(strEscapeSequencesRegExp, 'g')

// Escaped special characters. Use empty strings to fill up unused entries.
const meta = [
'\\u0000', '\\u0001', '\\u0002', '\\u0003', '\\u0004',
'\\u0005', '\\u0006', '\\u0007', '\\b', '\\t',
'\\n', '\\u000b', '\\f', '\\r', '\\u000e',
'\\u000f', '\\u0010', '\\u0011', '\\u0012', '\\u0013',
'\\u0014', '\\u0015', '\\u0016', '\\u0017', '\\u0018',
'\\u0019', '\\u001a', '\\u001b', '\\u001c', '\\u001d',
'\\u001e', '\\u001f', '', '', '\\"',
'', '', '', '', '', '', '', '', '', '',
'', '', '', '', '', '', '', '', '', '',
'', '', '', '', '', '', '', '', '', '',
'', '', '', '', '', '', '', '', '', '',
'', '', '', '', '', '', '', '', '', '',
'', '', '', '', '', '', '', '\\\\'
]

function escapeFn (str) {
if (str.length === 2) {
const charCode = str.charCodeAt(1)
return `${str[0]}\\u${charCode.toString(16)}`
}
const charCode = str.charCodeAt(0)
return meta.length > charCode
? meta[charCode]
: `\\u${charCode.toString(16)}`
}

// Escape C0 control characters, double quotes, the backslash and every code
// unit with a numeric value in the inclusive range 0xD800 to 0xDFFF.
function strEscape (str) {
// Some magic numbers that worked out fine while benchmarking with v8 8.0
if (str.length < 5000 && !strEscapeSequencesRegExp.test(str)) {
return str
}
if (str.length > 100) {
return str.replace(strEscapeSequencesReplacer, escapeFn)
}
let result = ''
let last = 0
for (let i = 0; i < str.length; i++) {
const point = str.charCodeAt(i)
if (point === 34 || point === 92 || point < 32) {
result += `${str.slice(last, i)}${meta[point]}`
last = i + 1
} else if (point >= 0xd800 && point <= 0xdfff) {
if (point <= 0xdbff && i + 1 < str.length) {
const nextPoint = str.charCodeAt(i + 1)
if (nextPoint >= 0xdc00 && nextPoint <= 0xdfff) {
i++
continue
}
}
result += `${str.slice(last, i)}\\u${point.toString(16)}`
last = i + 1
}
return `"${str}"`
}
result += str.slice(last)
return result
return JSON.stringify(str)
}

function insertSort (array) {
Expand Down Expand Up @@ -237,7 +185,7 @@ function configure (options) {

switch (typeof value) {
case 'string':
return `"${strEscape(value)}"`
return strEscape(value)
case 'object': {
if (value === null) {
return 'null'
Expand Down Expand Up @@ -313,7 +261,7 @@ function configure (options) {
const key = keys[i]
const tmp = stringifyFnReplacer(key, value, stack, replacer, spacer, indentation)
if (tmp !== undefined) {
res += `${separator}"${strEscape(key)}":${whitespace}${tmp}`
res += `${separator}${strEscape(key)}:${whitespace}${tmp}`
separator = join
}
}
Expand Down Expand Up @@ -351,7 +299,7 @@ function configure (options) {

switch (typeof value) {
case 'string':
return `"${strEscape(value)}"`
return strEscape(value)
case 'object': {
if (value === null) {
return 'null'
Expand Down Expand Up @@ -407,7 +355,7 @@ function configure (options) {
for (const key of replacer) {
const tmp = stringifyArrayReplacer(key, value[key], stack, replacer, spacer, indentation)
if (tmp !== undefined) {
res += `${separator}"${strEscape(key)}":${whitespace}${tmp}`
res += `${separator}${strEscape(key)}:${whitespace}${tmp}`
separator = join
}
}
Expand Down Expand Up @@ -436,7 +384,7 @@ function configure (options) {
function stringifyIndent (key, value, stack, spacer, indentation) {
switch (typeof value) {
case 'string':
return `"${strEscape(value)}"`
return strEscape(value)
case 'object': {
if (value === null) {
return 'null'
Expand Down Expand Up @@ -512,7 +460,7 @@ function configure (options) {
const key = keys[i]
const tmp = stringifyIndent(key, value[key], stack, spacer, indentation)
if (tmp !== undefined) {
res += `${separator}"${strEscape(key)}": ${tmp}`
res += `${separator}${strEscape(key)}: ${tmp}`
separator = join
}
}
Expand Down Expand Up @@ -546,7 +494,7 @@ function configure (options) {
function stringifySimple (key, value, stack) {
switch (typeof value) {
case 'string':
return `"${strEscape(value)}"`
return strEscape(value)
case 'object': {
if (value === null) {
return 'null'
Expand Down Expand Up @@ -616,7 +564,7 @@ function configure (options) {
const key = keys[i]
const tmp = stringifySimple(key, value[key], stack)
if (tmp !== undefined) {
res += `${separator}"${strEscape(key)}":${tmp}`
res += `${separator}${strEscape(key)}:${tmp}`
separator = ','
}
}
Expand Down

0 comments on commit dbc5143

Please sign in to comment.