Socket
Socket
Sign inDemoInstall

safe-stable-stringify

Package Overview
Dependencies
0
Maintainers
1
Versions
12
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 2.0.0 to 2.1.0

tsconfig.json

2

benchmark.js

@@ -5,3 +5,3 @@ 'use strict'

const suite = new Benchmark.Suite()
const stringify = require('.')
const stringify = require('.').configure({ deterministic: true })

@@ -8,0 +8,0 @@ // eslint-disable-next-line

# Changelog
## v2.1.0
- Added `maximumBreadth` option to limit stringification at a specific object or array "width" (number of properties / values)
- Added `maximumDepth` option to limit stringification at a specific nesting depth
- Implemented the [well formed stringify proposal](https://github.com/tc39/proposal-well-formed-stringify) that is now part of the spec
- Fixed maximum spacer length (10)
- Fixed TypeScript definition
- Fixed duplicated array replacer values serialized more than once
## v2.0.0

@@ -4,0 +13,0 @@

@@ -1,3 +0,3 @@

declare function stringify(value: any, replacer?: (key: string, value: any) => any, space?: string | number): string;
declare function stringify(value: any, replacer?: (number | string)[] | null, space?: string | number): string;
export function stringify(value: any, replacer?: (key: string, value: any) => any, space?: string | number): string;
export function stringify(value: any, replacer?: (number | string)[] | null, space?: string | number): string;

@@ -8,8 +8,12 @@ export interface StringifyOptions {

deterministic?: boolean,
maximumBreadth?: number,
maximumDepth?: number,
}
declare function configure(StringifyOptions): stringify;
export namespace stringify {
export function configure(options: StringifyOptions): typeof stringify;
}
stringify.configure = configure;
export function configure(options: StringifyOptions): typeof stringify;
export default stringify;
'use strict'
const stringify = main()
stringify.configure = main
const stringify = configure()
// @ts-expect-error
stringify.configure = configure
// @ts-expect-error
stringify.stringify = stringify
// @ts-expect-error
stringify.default = stringify
// @ts-expect-error used for named export
exports.stringify = stringify
// @ts-expect-error used for named export
exports.configure = configure
module.exports = stringify
// eslint-disable-next-line
const strEscapeSequencesRegExp = /[\x00-\x1f\x22\x5c]/
const strEscapeSequencesRegExp = /[\u0000-\u001f\u0022\u005c\ud800-\udfff]/
// eslint-disable-next-line
const strEscapeSequencesReplacer = /[\x00-\x1f\x22\x5c]/g
const strEscapeSequencesReplacer = /[\u0000-\u001f\u0022\u005c\ud800-\udfff]/g

@@ -32,9 +43,10 @@ // Escaped special characters. Use empty strings to fill up unused entries.

function escapeFn (str) {
return meta[str.charCodeAt(0)]
const charCode = str.charCodeAt(0)
return meta.length > charCode
? meta[charCode]
: `\\u${charCode.toString(16).padStart(4, '0')}`
}
// Escape control characters, double quotes and the backslash.
// Note: it is faster to run this only once for a big string instead of only for
// the parts that it is necessary for. But this is only true if we do not add
// extra indentation to the string before.
// 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) {

@@ -50,19 +62,13 @@ // Some magic numbers that worked out fine while benchmarking with v8 8.0

let last = 0
let i = 0
for (; i < str.length; i++) {
for (let i = 0; i < str.length; i++) {
const point = str.charCodeAt(i)
if (point === 34 || point === 92 || point < 32) {
if (last === i) {
result += meta[point]
} else {
result += `${str.slice(last, i)}${meta[point]}`
}
result += `${str.slice(last, i)}${meta[point]}`
last = i + 1
} else if (point >= 55296 && point <= 57343) {
result += `${str.slice(last, i)}${`\\u${point.toString(16).padStart(4, '0')}`}`
last = i + 1
}
}
if (last === 0) {
result = str
} else if (last !== i) {
result += str.slice(last)
}
result += str.slice(last)
return result

@@ -99,13 +105,13 @@ }

function isTypedArray (value) {
return typedArrayPrototypeGetSymbolToStringTag.call(value) !== undefined
function isTypedArrayWithEntries (value) {
return typedArrayPrototypeGetSymbolToStringTag.call(value) !== undefined && value.length !== 0
}
function stringifyTypedArray (array, separator) {
if (array.length === 0) {
return ''
function stringifyTypedArray (array, separator, maximumBreadth) {
if (array.length < maximumBreadth) {
maximumBreadth = array.length
}
const whitespace = separator === ',' ? '' : ' '
let res = `"0":${whitespace}${array[0]}`
for (let i = 1; i < array.length; i++) {
for (let i = 1; i < maximumBreadth; i++) {
res += `${separator}"${i}":${whitespace}${array[i]}`

@@ -128,29 +134,55 @@ }

function getBigIntOption (options) {
if (options && Object.prototype.hasOwnProperty.call(options, 'bigint')) {
var bigint = options.bigint
if (typeof bigint !== 'boolean') {
throw new TypeError('The "bigint" argument must be of type boolean')
function getBooleanOption (options, key) {
if (options && Object.prototype.hasOwnProperty.call(options, key)) {
var value = options[key]
if (typeof value !== 'boolean') {
throw new TypeError(`The "${key}" argument must be of type boolean`)
}
}
return bigint === undefined ? true : bigint
return value === undefined ? true : value
}
function getDeterministicOption (options) {
if (options && Object.prototype.hasOwnProperty.call(options, 'deterministic')) {
var deterministic = options.deterministic
if (typeof deterministic !== 'boolean') {
throw new TypeError('The "deterministic" argument must be of type boolean')
function getPositiveIntegerOption (options, key) {
if (options && Object.prototype.hasOwnProperty.call(options, key)) {
var value = options[key]
if (typeof value !== 'number') {
throw new TypeError(`The "${key}" argument must be of type number`)
}
if (!Number.isInteger(value)) {
throw new TypeError(`The "${key}" argument must be an integer`)
}
if (value < 1) {
throw new RangeError(`The "${key}" argument must be >= 1`)
}
}
return deterministic === undefined ? true : deterministic
return value === undefined ? Infinity : value
}
function main (options) {
function getItemCount (number) {
if (number === 1) {
return '1 item'
}
return `${number} items`
}
function getUniqueReplacerSet (replacerArray) {
const replacerSet = new Set()
for (const value of replacerArray) {
if (typeof value === 'string') {
replacerSet.add(value)
} else if (typeof value === 'number') {
replacerSet.add(String(value))
}
}
return replacerSet
}
function configure (options) {
const circularValue = getCircularValueOption(options)
const bigint = getBigIntOption(options)
const deterministic = getDeterministicOption(options)
const bigint = getBooleanOption(options, 'bigint')
const deterministic = getBooleanOption(options, 'deterministic')
const maximumDepth = getPositiveIntegerOption(options, 'maximumDepth')
const maximumBreadth = getPositiveIntegerOption(options, 'maximumBreadth')
// Full version: supports all options
function stringifyFullFn (key, parent, stack, replacer, spacer, indentation) {
function stringifyFnReplacer (key, parent, stack, replacer, spacer, indentation) {
let value = parent[key]

@@ -182,2 +214,5 @@

}
if (maximumDepth < stack.length + 1) {
return '"[Array]"'
}
stack.push(value)

@@ -189,10 +224,15 @@ if (spacer !== '') {

}
const maximumValuesToStringify = Math.min(value.length, maximumBreadth)
let i = 0
for (; i < value.length - 1; i++) {
const tmp = stringifyFullFn(i, value, stack, replacer, spacer, indentation)
for (; i < maximumValuesToStringify - 1; i++) {
const tmp = stringifyFnReplacer(i, value, stack, replacer, spacer, indentation)
res += tmp !== undefined ? tmp : 'null'
res += join
}
const tmp = stringifyFullFn(i, value, stack, replacer, spacer, indentation)
const tmp = stringifyFnReplacer(i, value, stack, replacer, spacer, indentation)
res += tmp !== undefined ? tmp : 'null'
if (value.length - 1 > maximumBreadth) {
const removedKeys = value.length - maximumBreadth - 1
res += `${join}"... ${getItemCount(removedKeys)} not stringified"`
}
if (spacer !== '') {

@@ -206,5 +246,9 @@ res += `\n${originalIndentation}`

let keys = Object.keys(value)
if (keys.length === 0) {
const keyLength = keys.length
if (keyLength === 0) {
return '{}'
}
if (maximumDepth < stack.length + 1) {
return '"[Object]"'
}
let whitespace = ''

@@ -217,5 +261,7 @@ let separator = ''

}
if (isTypedArray(value)) {
res += stringifyTypedArray(value, join)
let maximumPropertiesToStringify = Math.min(keyLength, maximumBreadth)
if (isTypedArrayWithEntries(value)) {
res += stringifyTypedArray(value, join, maximumBreadth)
keys = keys.slice(value.length)
maximumPropertiesToStringify -= value.length
separator = join

@@ -227,4 +273,5 @@ }

stack.push(value)
for (const key of keys) {
const tmp = stringifyFullFn(key, value, stack, replacer, spacer, indentation)
for (let i = 0; i < maximumPropertiesToStringify; i++) {
const key = keys[i]
const tmp = stringifyFnReplacer(key, value, stack, replacer, spacer, indentation)
if (tmp !== undefined) {

@@ -235,2 +282,7 @@ res += `${separator}"${strEscape(key)}":${whitespace}${tmp}`

}
if (keyLength > maximumBreadth) {
const removedKeys = keyLength - maximumBreadth
res += `${separator}"...":${whitespace}"${getItemCount(removedKeys)} not stringified"`
separator = join
}
if (spacer !== '' && separator.length > 1) {

@@ -251,3 +303,3 @@ res = `\n${indentation}${res}\n${originalIndentation}`

function stringifyFullArr (key, value, stack, replacer, spacer, indentation) {
function stringifyArrayReplacer (key, value, stack, replacer, spacer, indentation) {
if (typeof value === 'object' && value !== null && typeof value.toJSON === 'function') {

@@ -276,2 +328,5 @@ value = value.toJSON(key)

}
if (maximumDepth < stack.length + 1) {
return '"[Array]"'
}
stack.push(value)

@@ -283,10 +338,15 @@ if (spacer !== '') {

}
const maximumValuesToStringify = Math.min(value.length, maximumBreadth)
let i = 0
for (; i < value.length - 1; i++) {
const tmp = stringifyFullArr(i, value[i], stack, replacer, spacer, indentation)
for (; i < maximumValuesToStringify - 1; i++) {
const tmp = stringifyArrayReplacer(i, value[i], stack, replacer, spacer, indentation)
res += tmp !== undefined ? tmp : 'null'
res += join
}
const tmp = stringifyFullArr(i, value[i], stack, replacer, spacer, indentation)
const tmp = stringifyArrayReplacer(i, value[i], stack, replacer, spacer, indentation)
res += tmp !== undefined ? tmp : 'null'
if (value.length - 1 > maximumBreadth) {
const removedKeys = value.length - maximumBreadth - 1
res += `${join}"... ${getItemCount(removedKeys)} not stringified"`
}
if (spacer !== '') {

@@ -298,4 +358,3 @@ res += `\n${originalIndentation}`

}
if (replacer.length === 0) {
if (replacer.size === 0) {
return '{}'

@@ -312,8 +371,6 @@ }

for (const key of replacer) {
if (typeof key === 'string' || typeof key === 'number') {
const tmp = stringifyFullArr(key, value[key], stack, replacer, spacer, indentation)
if (tmp !== undefined) {
res += `${separator}"${strEscape(key)}":${whitespace}${tmp}`
separator = join
}
const tmp = stringifyArrayReplacer(key, value[key], stack, replacer, spacer, indentation)
if (tmp !== undefined) {
res += `${separator}"${strEscape(key)}":${whitespace}${tmp}`
separator = join
}

@@ -336,3 +393,2 @@ }

// Supports only the spacer option
function stringifyIndent (key, value, stack, spacer, indentation) {

@@ -365,2 +421,5 @@ switch (typeof value) {

}
if (maximumDepth < stack.length + 1) {
return '"[Array]"'
}
stack.push(value)

@@ -370,4 +429,5 @@ indentation += spacer

const join = `,\n${indentation}`
const maximumValuesToStringify = Math.min(value.length, maximumBreadth)
let i = 0
for (; i < value.length - 1; i++) {
for (; i < maximumValuesToStringify - 1; i++) {
const tmp = stringifyIndent(i, value[i], stack, spacer, indentation)

@@ -379,2 +439,6 @@ res += tmp !== undefined ? tmp : 'null'

res += tmp !== undefined ? tmp : 'null'
if (value.length - 1 > maximumBreadth) {
const removedKeys = value.length - maximumBreadth - 1
res += `${join}"... ${getItemCount(removedKeys)} not stringified"`
}
res += `\n${originalIndentation}`

@@ -386,5 +450,9 @@ stack.pop()

let keys = Object.keys(value)
if (keys.length === 0) {
const keyLength = keys.length
if (keyLength === 0) {
return '{}'
}
if (maximumDepth < stack.length + 1) {
return '"[Object]"'
}
indentation += spacer

@@ -394,5 +462,7 @@ const join = `,\n${indentation}`

let separator = ''
if (isTypedArray(value)) {
res += stringifyTypedArray(value, join)
let maximumPropertiesToStringify = Math.min(keyLength, maximumBreadth)
if (isTypedArrayWithEntries(value)) {
res += stringifyTypedArray(value, join, maximumBreadth)
keys = keys.slice(value.length)
maximumPropertiesToStringify -= value.length
separator = join

@@ -404,3 +474,4 @@ }

stack.push(value)
for (const key of keys) {
for (let i = 0; i < maximumPropertiesToStringify; i++) {
const key = keys[i]
const tmp = stringifyIndent(key, value[key], stack, spacer, indentation)

@@ -412,2 +483,7 @@ if (tmp !== undefined) {

}
if (keyLength > maximumBreadth) {
const removedKeys = keyLength - maximumBreadth
res += `${separator}"...": "${getItemCount(removedKeys)} not stringified"`
separator = join
}
if (separator !== '') {

@@ -428,3 +504,2 @@ res = `\n${indentation}${res}\n${originalIndentation}`

// Simple without any options
function stringifySimple (key, value, stack) {

@@ -458,5 +533,9 @@ switch (typeof value) {

}
if (maximumDepth < stack.length + 1) {
return '"[Array]"'
}
stack.push(value)
const maximumValuesToStringify = Math.min(value.length, maximumBreadth)
let i = 0
for (; i < value.length - 1; i++) {
for (; i < maximumValuesToStringify - 1; i++) {
const tmp = stringifySimple(i, value[i], stack)

@@ -468,2 +547,6 @@ res += tmp !== undefined ? tmp : 'null'

res += tmp !== undefined ? tmp : 'null'
if (value.length - 1 > maximumBreadth) {
const removedKeys = value.length - maximumBreadth - 1
res += `,"... ${getItemCount(removedKeys)} not stringified"`
}
stack.pop()

@@ -474,9 +557,16 @@ return `[${res}]`

let keys = Object.keys(value)
if (keys.length === 0) {
const keyLength = keys.length
if (keyLength === 0) {
return '{}'
}
if (maximumDepth < stack.length + 1) {
return '"[Object]"'
}
let separator = ''
if (isTypedArray(value)) {
res += stringifyTypedArray(value, ',')
let maximumPropertiesToStringify = Math.min(keyLength, maximumBreadth)
if (isTypedArrayWithEntries(value)) {
res += stringifyTypedArray(value, ',', maximumBreadth)
keys = keys.slice(value.length)
maximumPropertiesToStringify -= value.length
separator = ','
}

@@ -487,3 +577,4 @@ if (deterministic) {

stack.push(value)
for (const key of keys) {
for (let i = 0; i < maximumPropertiesToStringify; i++) {
const key = keys[i]
const tmp = stringifySimple(key, value[key], stack)

@@ -495,2 +586,6 @@ if (tmp !== undefined) {

}
if (keyLength > maximumBreadth) {
const removedKeys = keyLength - maximumBreadth
res += `${separator}"...":"${getItemCount(removedKeys)} not stringified"`
}
stack.pop()

@@ -512,15 +607,15 @@ return `{${res}}`

if (typeof space === 'number') {
spacer = ' '.repeat(space)
spacer = ' '.repeat(Math.min(space, 10))
} else if (typeof space === 'string') {
spacer = space
spacer = space.slice(0, 10)
}
if (replacer != null) {
if (typeof replacer === 'function') {
return stringifyFullFn('', { '': value }, [], replacer, spacer, '')
return stringifyFnReplacer('', { '': value }, [], replacer, spacer, '')
}
if (Array.isArray(replacer)) {
return stringifyFullArr('', value, [], replacer, spacer, '')
return stringifyArrayReplacer('', value, [], getUniqueReplacerSet(replacer), spacer, '')
}
}
if (spacer !== '') {
if (spacer.length !== 0) {
return stringifyIndent('', value, [], spacer, '')

@@ -527,0 +622,0 @@ }

{
"name": "safe-stable-stringify",
"version": "2.0.0",
"version": "2.1.0",
"description": "Deterministic and safely JSON.stringify to quickly serialize JavaScript objects",

@@ -26,11 +26,18 @@ "exports": {

"test": "standard && tap test.js",
"tap": "tap test.js",
"tap:only": "tap test.js --watch --only",
"benchmark": "node benchmark.js",
"compare": "node compare.js",
"lint": "standard --fix"
"lint": "standard --fix",
"tsc": "tsc"
},
"engines": {
"node": ">=10"
},
"author": "Ruben Bridgewater",
"license": "MIT",
"typings": "index",
"typings": "index.d.ts",
"devDependencies": {
"@types/json-stable-stringify": "^1.0.32",
"@types/node": "^16.11.1",
"benchmark": "^2.1.4",

@@ -46,3 +53,4 @@ "clone": "^2.1.2",

"standard": "^15.0.0",
"tap": "^15.0.9"
"tap": "^15.0.9",
"typescript": "^4.4.3"
},

@@ -49,0 +57,0 @@ "repository": {

@@ -50,2 +50,10 @@ # safe-stable-stringify

instead of relying on the insertion order. **Default:** `true`.
* `maximumBreadth` {number} Maximum number of entries to serialize per object
(at least one). The serialized output contains information about how many
entries have not been serialized. Ignored properties are counted as well
(e.g., properties with symbol values). Using the array replacer overrules this
option. **Default:** `Infinity`
* `maximumDepth` {number} Maximum number of object nesting levels (at least 1)
that will be serialized. Objects at the maximum level are serialized as
`'[Object]'` and arrays as `'[Array]'`. **Default:** `Infinity`
* Returns: {function} A stringify function with the options applied.

@@ -60,2 +68,4 @@

deterministic: false,
maximumDepth: 1,
maximumBreadth: 4
})

@@ -69,2 +79,4 @@

circular.circular = circular
circular.ignored = true
circular.alsoIgnored = 'Yes!'

@@ -76,9 +88,6 @@ const stringified = stringify(circular, null, 4)

// "bigint": 999999999999999999,
// "typed": {
// "0": 0,
// "1": 0,
// "2": 0
// },
// "typed": "[Object]",
// "deterministic": "I don't think so",
// "circular": "Magic circle!"
// "circular": "Magic circle!",
// "...": "2 items not stringified"
// }

@@ -92,6 +101,8 @@ ```

1. BigInt values are stringified as regular number instead of throwing a TypeError.
1. Boxed primitives (e.g., `Number(5)`) are not unboxed and are handled as
regular object.
Those are the only differences to the real JSON.stringify. This is a side effect
free variant and [`toJSON`][], [`replacer`][] and the [`spacer`][] work the same
as with the native JSON.stringify.
Those are the only differences to `JSON.stringify()`. This is a side effect free
variant and [`toJSON`][], [`replacer`][] and the [`spacer`][] work the same as
with `JSON.stringify()`.

@@ -103,19 +114,24 @@ ## Performance / Benchmarks

(Lenovo T450s with a i7-5600U CPU using Node.js 8.9.4)
(Dell Precision 5540, i7-9850H CPU @ 2.60GHz, Node.js 16.11.1)
```md
simple: simple object x 1,733,045 ops/sec ±1.82% (86 runs sampled)
simple: circular x 717,021 ops/sec ±0.78% (91 runs sampled)
simple: deep x 17,674 ops/sec ±0.77% (94 runs sampled)
simple: deep circular x 17,396 ops/sec ±0.70% (93 runs sampled)
simple: simple object x 3,463,894 ops/sec ±0.44% (98 runs sampled)
simple: circular x 1,236,007 ops/sec ±0.46% (99 runs sampled)
simple: deep x 18,942 ops/sec ±0.41% (93 runs sampled)
simple: deep circular x 18,690 ops/sec ±0.72% (96 runs sampled)
replacer: simple object x 1,126,942 ops/sec ±2.22% (91 runs sampled)
replacer: circular x 541,243 ops/sec ±0.87% (94 runs sampled)
replacer: deep x 17,229 ops/sec ±0.90% (94 runs sampled)
replacer: deep circular x 16,948 ops/sec ±0.86% (97 runs sampled)
replacer: simple object x 2,664,940 ops/sec ±0.31% (98 runs sampled)
replacer: circular x 1,015,981 ops/sec ±0.09% (99 runs sampled)
replacer: deep x 17,328 ops/sec ±0.38% (97 runs sampled)
replacer: deep circular x 17,071 ops/sec ±0.21% (98 runs sampled)
array: simple object x 1,470,751 ops/sec ±0.84% (95 runs sampled)
array: circular x 1,360,269 ops/sec ±2.94% (91 runs sampled)
array: deep x 1,289,785 ops/sec ±2.82% (87 runs sampled)
array: deep circular x 1,400,577 ops/sec ±1.00% (92 runs sampled)
array: simple object x 3,869,608 ops/sec ±0.22% (98 runs sampled)
array: circular x 3,853,943 ops/sec ±0.45% (96 runs sampled)
array: deep x 3,563,227 ops/sec ±0.20% (100 runs sampled)
array: deep circular x 3,286,475 ops/sec ±0.07% (100 runs sampled)
indentation: simple object x 2,183,162 ops/sec ±0.66% (97 runs sampled)
indentation: circular x 872,538 ops/sec ±0.57% (98 runs sampled)
indentation: deep x 16,795 ops/sec ±0.48% (93 runs sampled)
indentation: deep circular x 16,443 ops/sec ±0.40% (97 runs sampled)
```

@@ -126,9 +142,9 @@

```md
fast-json-stable-stringify x 9,336 ops/sec ±0.64% (90 runs sampled)
json-stable-stringify x 7,512 ops/sec ±0.63% (91 runs sampled)
fast-stable-stringify x 11,674 ops/sec ±0.58% (92 runs sampled)
faster-stable-stringify x 8,893 ops/sec ±0.51% (92 runs sampled)
json-stringify-deterministic x 6,240 ops/sec ±0.68% (94 runs sampled)
fast-safe-stringify x 15,939 ops/sec ±0.42% (96 runs sampled)
this x 24,048 ops/sec ±0.44% (91 runs sampled)
fast-json-stable-stringify x 18,765 ops/sec ±0.71% (94 runs sampled)
json-stable-stringify x 13,870 ops/sec ±0.72% (94 runs sampled)
fast-stable-stringify x 21,343 ops/sec ±0.33% (95 runs sampled)
faster-stable-stringify x 17,707 ops/sec ±0.44% (97 runs sampled)
json-stringify-deterministic x 11,208 ops/sec ±0.57% (98 runs sampled)
fast-safe-stringify x 21,460 ops/sec ±0.75% (99 runs sampled)
this x 30,367 ops/sec ±0.39% (96 runs sampled)

@@ -135,0 +151,0 @@ The fastest is this

const { test } = require('tap')
const stringify = require('./')
const { stringify } = require('./index.js')
const clone = require('clone')

@@ -98,8 +98,8 @@

fixture.push(
{ name: 'Jon Snow', bastards: fixture },
{ name: 'Ramsay Bolton', bastards: fixture }
{ name: 'Jon Snow', circular: fixture },
{ name: 'Ramsay Bolton', circular: fixture }
)
const expected = JSON.stringify([
{ bastards: '[Circular]', name: 'Jon Snow' },
{ bastards: '[Circular]', name: 'Ramsay Bolton' }
{ circular: '[Circular]', name: 'Jon Snow' },
{ circular: '[Circular]', name: 'Ramsay Bolton' }
])

@@ -158,5 +158,5 @@ const actual = stringify(fixture)

test('child circular reference with toJSON', function (assert) {
// Create a test object that has an overriden `toJSON` property
// Create a test object that has an overridden `toJSON` property
TestObject.prototype.toJSON = function () { return { special: 'case' } }
function TestObject (content) {}
function TestObject () {}

@@ -166,2 +166,3 @@ // Creating a simple circular object structure

parentObject.childObject = new TestObject()
// @ts-expect-error
parentObject.childObject.parentObject = parentObject

@@ -171,7 +172,11 @@

const otherParentObject = new TestObject()
// @ts-expect-error
otherParentObject.otherChildObject = {}
// @ts-expect-error
otherParentObject.otherChildObject.otherParentObject = otherParentObject
// Making sure our original tests work
// @ts-expect-error
assert.same(parentObject.childObject.parentObject, parentObject)
// @ts-expect-error
assert.same(otherParentObject.otherChildObject.otherParentObject, otherParentObject)

@@ -184,3 +189,5 @@

// Therefore the following assertion should be `true`
// @ts-expect-error
assert.same(parentObject.childObject.parentObject, parentObject)
// @ts-expect-error
assert.same(otherParentObject.otherChildObject.otherParentObject, otherParentObject)

@@ -220,2 +227,3 @@

toJSON: function () {
// @ts-expect-error
a.b = 2

@@ -228,2 +236,3 @@ return '[Redacted]'

toJSON: function () {
// @ts-expect-error
a.baz = circle

@@ -260,2 +269,3 @@ return '[Redacted]'

// @ts-expect-error
const actual = stringify(obj, 'invalidReplacer')

@@ -425,3 +435,5 @@ const expected = stringify(obj)

const obj = [null, null, [], {}]
// @ts-expect-error
const expected = JSON.stringify(obj, [false], 3)
// @ts-expect-error
const actual = stringify(obj, [false], 3)

@@ -455,2 +467,3 @@ assert.equal(actual, expected)

// @ts-expect-error
obj = { b: 'hello', a: undefined, c: 1 }

@@ -554,2 +567,3 @@

// @ts-expect-error
assert.throws(() => stringify.configure({ circularValue: { objects: 'are not allowed' } }), /circularValue/)

@@ -572,2 +586,3 @@

// @ts-expect-error
assert.throws(() => stringify.configure({ deterministic: 1 }), /deterministic/)

@@ -616,6 +631,20 @@

const obj = new Uint8Array(0)
// @ts-expect-error
obj.foo = true
const expected = JSON.stringify(obj)
const actual = stringify(obj)
let expected = JSON.stringify(obj)
let actual = stringify(obj)
assert.equal(actual, expected)
expected = JSON.stringify(obj, null, 2)
actual = stringify(obj, null, 2)
assert.equal(actual, expected)
expected = JSON.stringify(obj, ['foo'])
actual = stringify(obj, ['foo'])
assert.equal(actual, expected)
expected = JSON.stringify(obj, (a, b) => b)
actual = stringify(obj, (a, b) => b)
assert.equal(actual, expected)
assert.end()

@@ -625,12 +654,31 @@ })

test('trigger sorting fast path for objects with lots of properties', function (assert) {
const keys = []
const obj = {}
for (let i = 0; i < 1e4; i++) {
obj[`a${i}`] = i
keys.push(`a${i}`)
}
const start = Date.now()
stringify(obj)
assert.ok(Date.now() - start < 100)
const now = Date.now()
const actualTime = now - start
keys.sort()
const expectedTime = Date.now() - now
assert.ok(Math.abs(actualTime - expectedTime) < 50)
assert.end()
})
test('maximum spacer length', function (assert) {
const input = { a: 0 }
const expected = `{\n${' '.repeat(10)}"a": 0\n}`
assert.equal(stringify(input, null, 11), expected)
assert.equal(stringify(input, null, 1e5), expected)
assert.equal(stringify(input, null, ' '.repeat(11)), expected)
assert.equal(stringify(input, null, ' '.repeat(1e3)), expected)
assert.end()
})
test('indent properly; regression test for issue #16', function (assert) {

@@ -670,2 +718,3 @@ const o = {

assert.equal(
// @ts-ignore
stringify(o, (k, v) => v, 2),

@@ -689,2 +738,3 @@ indentedJSONReplacer

assert.equal(
// @ts-ignore
stringify(o, (k, v) => v, 2),

@@ -696,1 +746,287 @@ indentedJSONReplacer.replace(circularIdentifier, circularReplacement)

})
test('should stop if max depth is reached', (assert) => {
const serialize = stringify.configure({
maximumDepth: 5
})
const nested = {}
const MAX_DEPTH = 10
let currentNestedObject = null
for (let i = 0; i < MAX_DEPTH; i++) {
const k = 'nest_' + i
if (!currentNestedObject) {
currentNestedObject = nested
}
currentNestedObject[k] = {
foo: 'bar'
}
currentNestedObject = currentNestedObject[k]
}
const res = serialize(nested)
assert.ok(res.indexOf('"nest_4":"[Object]"'))
assert.end()
})
test('should serialize only first 10 elements', (assert) => {
const serialize = stringify.configure({
maximumBreadth: 10
})
const breadth = {}
const MAX_BREADTH = 100
for (let i = 0; i < MAX_BREADTH; i++) {
const k = 'key_' + i
breadth[k] = 'foobar'
}
const res = serialize(breadth)
const expected = '{"key_0":"foobar","key_1":"foobar","key_10":"foobar","key_11":"foobar","key_12":"foobar","key_13":"foobar","key_14":"foobar","key_15":"foobar","key_16":"foobar","key_17":"foobar","...":"90 items not stringified"}'
assert.equal(res, expected)
assert.end()
})
test('should serialize only first 10 elements with custom replacer and indentation', (assert) => {
const serialize = stringify.configure({
maximumBreadth: 10,
maximumDepth: 1
})
const breadth = { a: Array.from({ length: 100 }, (_, i) => i) }
const MAX_BREADTH = 100
for (let i = 0; i < MAX_BREADTH; i++) {
const k = 'key_' + i
breadth[k] = 'foobar'
}
const res = serialize(breadth, (k, v) => v, 2)
const expected = `{
"a": "[Array]",
"key_0": "foobar",
"key_1": "foobar",
"key_10": "foobar",
"key_11": "foobar",
"key_12": "foobar",
"key_13": "foobar",
"key_14": "foobar",
"key_15": "foobar",
"key_16": "foobar",
"...": "91 items not stringified"
}`
assert.equal(res, expected)
assert.end()
})
test('maximumDepth config', function (assert) {
const obj = { a: { b: { c: 1 }, a: [1, 2, 3] } }
const serialize = stringify.configure({
maximumDepth: 2
})
const result = serialize(obj, (key, val) => val)
assert.equal(result, '{"a":{"a":"[Array]","b":"[Object]"}}')
const res2 = serialize(obj, ['a', 'b'])
assert.equal(res2, '{"a":{"a":"[Array]","b":{}}}')
const json = JSON.stringify(obj, ['a', 'b'])
assert.equal(json, '{"a":{"a":[1,2,3],"b":{}}}')
const res3 = serialize(obj, null, 2)
assert.equal(res3, `{
"a": {
"a": "[Array]",
"b": "[Object]"
}
}`)
const res4 = serialize(obj)
assert.equal(res4, '{"a":{"a":"[Array]","b":"[Object]"}}')
assert.end()
})
test('maximumBreadth config', function (assert) {
const obj = { a: ['a', 'b', 'c', 'd', 'e'] }
const serialize = stringify.configure({
maximumBreadth: 3
})
const result = serialize(obj, (key, val) => val)
assert.equal(result, '{"a":["a","b","c","... 1 item not stringified"]}')
const res2 = serialize(obj, ['a', 'b'])
assert.equal(res2, '{"a":["a","b","c","... 1 item not stringified"]}')
const res3 = serialize(obj, null, 2)
assert.equal(res3, `{
"a": [
"a",
"b",
"c",
"... 1 item not stringified"
]
}`)
const res4 = serialize({ a: { a: 1, b: 1, c: 1, d: 1, e: 1 } }, null, 2)
assert.equal(res4, `{
"a": {
"a": 1,
"b": 1,
"c": 1,
"...": "2 items not stringified"
}
}`)
assert.end()
})
test('limit number of keys with array replacer', function (assert) {
const replacer = ['a', 'b', 'c', 'd', 'e']
const obj = {
a: 'a',
b: 'b',
c: 'c',
d: 'd',
e: 'e',
f: 'f',
g: 'g',
h: 'h'
}
const serialize = stringify.configure({
maximumBreadth: 3
})
const res = serialize(obj, replacer, 2)
const expected = `{
"a": "a",
"b": "b",
"c": "c",
"d": "d",
"e": "e"
}`
assert.equal(res, expected)
assert.end()
})
test('limit number of keys in array', (assert) => {
const serialize = stringify.configure({
maximumBreadth: 3
})
const arr = []
const MAX_BREADTH = 100
for (let i = 0; i < MAX_BREADTH; i++) {
arr.push(i)
}
const res = serialize(arr)
const expected = '[0,1,2,"... 96 items not stringified"]'
assert.equal(res, expected)
assert.end()
})
test('limit number of keys in typed array', (assert) => {
const serialize = stringify.configure({
maximumBreadth: 3
})
const MAX = 100
const arr = new Int32Array(MAX)
for (let i = 0; i < MAX; i++) {
arr[i] = i
}
// @ts-expect-error we want to explicitly test this behavior.
arr.foobar = true
const res = serialize(arr)
const expected = '{"0":0,"1":1,"2":2,"...":"98 items not stringified"}'
assert.equal(res, expected)
const res2 = serialize(arr, (a, b) => b)
assert.equal(res2, expected)
const res3 = serialize(arr, [0, 1, 2])
assert.equal(res3, '{"0":0,"1":1,"2":2}')
const res4 = serialize(arr, null, 4)
assert.equal(res4, `{
"0": 0,
"1": 1,
"2": 2,
"...": "98 items not stringified"
}`)
assert.end()
})
test('show skipped keys even non were serliazable', (assert) => {
const serialize = stringify.configure({
maximumBreadth: 1
})
const input = { a: Symbol('ignored'), b: Symbol('ignored') }
let actual = serialize(input)
let expected = '{"...":"1 item not stringified"}'
assert.equal(actual, expected)
actual = serialize(input, (a, b) => b)
assert.equal(actual, expected)
actual = serialize(input, null, 1)
expected = '{\n "...": "1 item not stringified"\n}'
assert.equal(actual, expected)
actual = serialize(input, (a, b) => b, 1)
assert.equal(actual, expected)
actual = serialize(input, ['a'])
expected = '{}'
assert.equal(actual, expected)
actual = serialize(input, ['a', 'b', 'c'])
assert.equal(actual, expected)
assert.end()
})
test('array replacer entries are unique', (assert) => {
const input = { 0: 0, b: 1 }
const replacer = ['b', {}, [], 0, 'b', '0']
// @ts-expect-error
const actual = stringify(input, replacer)
// @ts-expect-error
const expected = JSON.stringify(input, replacer)
assert.equal(actual, expected)
assert.end()
})
test('should throw when maximumBreadth receives malformed input', (assert) => {
assert.throws(() => {
stringify.configure({
// @ts-expect-error
maximumBreadth: '3'
})
})
assert.throws(() => {
stringify.configure({
maximumBreadth: 3.1
})
})
assert.throws(() => {
stringify.configure({
maximumBreadth: 0
})
})
assert.end()
})
test('check for well formed stringify implementation', (assert) => {
for (let i = 0; i < 2 ** 16; i++) {
const string = String.fromCharCode(i)
const actual = stringify(string)
const expected = JSON.stringify(string)
// Older Node.js versions do not use the well formed JSON implementation.
if (Number(process.version.split('.')[0].slice(1)) >= 12 || i < 0xd800 || i > 0xdfff) {
assert.equal(actual, expected)
} else {
assert.not(actual, expected)
}
}
// Trigger special case
const longStringEscape = stringify(`${'a'.repeat(100)}\uD800`)
assert.equal(longStringEscape, `"${'a'.repeat(100)}\\ud800"`)
assert.end()
})

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc