safe-stable-stringify
Advanced tools
Comparing version 1.1.1 to 2.0.0
@@ -7,3 +7,4 @@ 'use strict' | ||
const array = new Array(10).fill(0).map((_, i) => i) | ||
// eslint-disable-next-line | ||
const array = Array({ length: 10 }, (_, i) => i) | ||
const obj = { array } | ||
@@ -10,0 +11,0 @@ const circ = JSON.parse(JSON.stringify(obj)) |
# Changelog | ||
## v2.0.0 | ||
- __[BREAKING]__ Convert BigInt to number by default instead of ignoring these values | ||
If you wish to ignore these values similar to earlier versions, just use the new `bigint` option and set it to `false`. | ||
- __[BREAKING]__ Support ESM | ||
- __[BREAKING]__ Requires ES6 | ||
- Optional BigInt support | ||
- Deterministic behavior is now optional | ||
- The value to indicate a circular structure is now adjustable | ||
- Significantly faster TypedArray stringification | ||
- Smaller Codebase | ||
- Removed stateful indentation to guarantee side-effect freeness | ||
## v1.1.1 | ||
@@ -8,1 +21,9 @@ | ||
## v1.1.0 | ||
- Add support for IE11 (https://github.com/BridgeAR/safe-stable-stringify/commit/917b6128de135a950ec178d66d86b4d772c7656d) | ||
- Fix issue with undefined values (https://github.com/BridgeAR/safe-stable-stringify/commit/4196f87, https://github.com/BridgeAR/safe-stable-stringify/commit/4eab558) | ||
- Fix typescript definition (https://github.com/BridgeAR/safe-stable-stringify/commit/7a87478) | ||
- Improve code coverage (https://github.com/BridgeAR/safe-stable-stringify/commit/ed8cadc, https://github.com/BridgeAR/safe-stable-stringify/commit/b58c494) | ||
- Update dev dependencies (https://github.com/BridgeAR/safe-stable-stringify/commit/b857ea8) | ||
- Improve docs |
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 interface StringifyOptions { | ||
bigint?: boolean, | ||
circularValue?: string | null, | ||
deterministic?: boolean, | ||
} | ||
declare function configure(StringifyOptions): stringify; | ||
stringify.configure = configure; | ||
export default stringify; |
501
index.js
'use strict' | ||
const stringify = require('./stable') | ||
const stringify = main() | ||
stringify.configure = main | ||
stringify.default = stringify | ||
module.exports = stringify | ||
stringify.default = stringify | ||
// eslint-disable-next-line | ||
const strEscapeSequencesRegExp = /[\x00-\x1f\x22\x5c]/ | ||
// eslint-disable-next-line | ||
const strEscapeSequencesReplacer = /[\x00-\x1f\x22\x5c]/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) { | ||
return meta[str.charCodeAt(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. | ||
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 | ||
let i = 0 | ||
for (; 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]}` | ||
} | ||
last = i + 1 | ||
} | ||
} | ||
if (last === 0) { | ||
result = str | ||
} else if (last !== i) { | ||
result += str.slice(last) | ||
} | ||
return result | ||
} | ||
function insertSort (array) { | ||
// Insertion sort is very efficient for small input sizes but it has a bad | ||
// worst case complexity. Thus, use native array sort for bigger values. | ||
if (array.length > 2e2) { | ||
return array.sort() | ||
} | ||
for (let i = 1; i < array.length; i++) { | ||
const currentValue = array[i] | ||
let position = i | ||
while (position !== 0 && array[position - 1] > currentValue) { | ||
array[position] = array[position - 1] | ||
position-- | ||
} | ||
array[position] = currentValue | ||
} | ||
return array | ||
} | ||
const typedArrayPrototypeGetSymbolToStringTag = | ||
Object.getOwnPropertyDescriptor( | ||
Object.getPrototypeOf( | ||
Object.getPrototypeOf( | ||
new Uint8Array() | ||
) | ||
), | ||
Symbol.toStringTag | ||
).get | ||
function isTypedArray (value) { | ||
return typedArrayPrototypeGetSymbolToStringTag.call(value) !== undefined | ||
} | ||
function stringifyTypedArray (array, separator) { | ||
if (array.length === 0) { | ||
return '' | ||
} | ||
const whitespace = separator === ',' ? '' : ' ' | ||
let res = `"0":${whitespace}${array[0]}` | ||
for (let i = 1; i < array.length; i++) { | ||
res += `${separator}"${i}":${whitespace}${array[i]}` | ||
} | ||
return res | ||
} | ||
function getCircularValueOption (options) { | ||
if (options && Object.prototype.hasOwnProperty.call(options, 'circularValue')) { | ||
var circularValue = options.circularValue | ||
if (typeof circularValue === 'string') { | ||
circularValue = `"${circularValue}"` | ||
} else if (circularValue !== null) { | ||
throw new TypeError('The "circularValue" argument must be of type string or the value null') | ||
} | ||
} | ||
return circularValue === undefined ? '"[Circular]"' : circularValue | ||
} | ||
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') | ||
} | ||
} | ||
return bigint === undefined ? true : bigint | ||
} | ||
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') | ||
} | ||
} | ||
return deterministic === undefined ? true : deterministic | ||
} | ||
function main (options) { | ||
const circularValue = getCircularValueOption(options) | ||
const bigint = getBigIntOption(options) | ||
const deterministic = getDeterministicOption(options) | ||
// Full version: supports all options | ||
function stringifyFullFn (key, parent, stack, replacer, spacer, indentation) { | ||
let value = parent[key] | ||
if (typeof value === 'object' && value !== null && typeof value.toJSON === 'function') { | ||
value = value.toJSON(key) | ||
} | ||
value = replacer.call(parent, key, value) | ||
switch (typeof value) { | ||
case 'string': | ||
return `"${strEscape(value)}"` | ||
case 'object': { | ||
if (value === null) { | ||
return 'null' | ||
} | ||
if (stack.indexOf(value) !== -1) { | ||
return circularValue | ||
} | ||
let res = '' | ||
let join = ',' | ||
const originalIndentation = indentation | ||
if (Array.isArray(value)) { | ||
if (value.length === 0) { | ||
return '[]' | ||
} | ||
stack.push(value) | ||
if (spacer !== '') { | ||
indentation += spacer | ||
res += `\n${indentation}` | ||
join = `,\n${indentation}` | ||
} | ||
let i = 0 | ||
for (; i < value.length - 1; i++) { | ||
const tmp = stringifyFullFn(i, value, stack, replacer, spacer, indentation) | ||
res += tmp !== undefined ? tmp : 'null' | ||
res += join | ||
} | ||
const tmp = stringifyFullFn(i, value, stack, replacer, spacer, indentation) | ||
res += tmp !== undefined ? tmp : 'null' | ||
if (spacer !== '') { | ||
res += `\n${originalIndentation}` | ||
} | ||
stack.pop() | ||
return `[${res}]` | ||
} | ||
let keys = Object.keys(value) | ||
if (keys.length === 0) { | ||
return '{}' | ||
} | ||
let whitespace = '' | ||
let separator = '' | ||
if (spacer !== '') { | ||
indentation += spacer | ||
join = `,\n${indentation}` | ||
whitespace = ' ' | ||
} | ||
if (isTypedArray(value)) { | ||
res += stringifyTypedArray(value, join) | ||
keys = keys.slice(value.length) | ||
separator = join | ||
} | ||
if (deterministic) { | ||
keys = insertSort(keys) | ||
} | ||
stack.push(value) | ||
for (const key of keys) { | ||
const tmp = stringifyFullFn(key, value, stack, replacer, spacer, indentation) | ||
if (tmp !== undefined) { | ||
res += `${separator}"${strEscape(key)}":${whitespace}${tmp}` | ||
separator = join | ||
} | ||
} | ||
if (spacer !== '' && separator.length > 1) { | ||
res = `\n${indentation}${res}\n${originalIndentation}` | ||
} | ||
stack.pop() | ||
return `{${res}}` | ||
} | ||
case 'number': | ||
return isFinite(value) ? String(value) : 'null' | ||
case 'boolean': | ||
return value === true ? 'true' : 'false' | ||
case 'bigint': | ||
return bigint ? String(value) : undefined | ||
} | ||
} | ||
function stringifyFullArr (key, value, stack, replacer, spacer, indentation) { | ||
if (typeof value === 'object' && value !== null && typeof value.toJSON === 'function') { | ||
value = value.toJSON(key) | ||
} | ||
switch (typeof value) { | ||
case 'string': | ||
return `"${strEscape(value)}"` | ||
case 'object': { | ||
if (value === null) { | ||
return 'null' | ||
} | ||
if (stack.indexOf(value) !== -1) { | ||
return circularValue | ||
} | ||
const originalIndentation = indentation | ||
let res = '' | ||
let join = ',' | ||
if (Array.isArray(value)) { | ||
if (value.length === 0) { | ||
return '[]' | ||
} | ||
stack.push(value) | ||
if (spacer !== '') { | ||
indentation += spacer | ||
res += `\n${indentation}` | ||
join = `,\n${indentation}` | ||
} | ||
let i = 0 | ||
for (; i < value.length - 1; i++) { | ||
const tmp = stringifyFullArr(i, value[i], stack, replacer, spacer, indentation) | ||
res += tmp !== undefined ? tmp : 'null' | ||
res += join | ||
} | ||
const tmp = stringifyFullArr(i, value[i], stack, replacer, spacer, indentation) | ||
res += tmp !== undefined ? tmp : 'null' | ||
if (spacer !== '') { | ||
res += `\n${originalIndentation}` | ||
} | ||
stack.pop() | ||
return `[${res}]` | ||
} | ||
if (replacer.length === 0) { | ||
return '{}' | ||
} | ||
stack.push(value) | ||
let whitespace = '' | ||
if (spacer !== '') { | ||
indentation += spacer | ||
join = `,\n${indentation}` | ||
whitespace = ' ' | ||
} | ||
let separator = '' | ||
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 | ||
} | ||
} | ||
} | ||
if (spacer !== '' && separator.length > 1) { | ||
res = `\n${indentation}${res}\n${originalIndentation}` | ||
} | ||
stack.pop() | ||
return `{${res}}` | ||
} | ||
case 'number': | ||
return isFinite(value) ? String(value) : 'null' | ||
case 'boolean': | ||
return value === true ? 'true' : 'false' | ||
case 'bigint': | ||
return bigint ? String(value) : undefined | ||
} | ||
} | ||
// Supports only the spacer option | ||
function stringifyIndent (key, value, stack, spacer, indentation) { | ||
switch (typeof value) { | ||
case 'string': | ||
return `"${strEscape(value)}"` | ||
case 'object': { | ||
if (value === null) { | ||
return 'null' | ||
} | ||
if (typeof value.toJSON === 'function') { | ||
value = value.toJSON(key) | ||
// Prevent calling `toJSON` again. | ||
if (typeof value !== 'object') { | ||
return stringifyIndent(key, value, stack, spacer, indentation) | ||
} | ||
if (value === null) { | ||
return 'null' | ||
} | ||
} | ||
if (stack.indexOf(value) !== -1) { | ||
return circularValue | ||
} | ||
const originalIndentation = indentation | ||
if (Array.isArray(value)) { | ||
if (value.length === 0) { | ||
return '[]' | ||
} | ||
stack.push(value) | ||
indentation += spacer | ||
let res = `\n${indentation}` | ||
const join = `,\n${indentation}` | ||
let i = 0 | ||
for (; i < value.length - 1; i++) { | ||
const tmp = stringifyIndent(i, value[i], stack, spacer, indentation) | ||
res += tmp !== undefined ? tmp : 'null' | ||
res += join | ||
} | ||
const tmp = stringifyIndent(i, value[i], stack, spacer, indentation) | ||
res += tmp !== undefined ? tmp : 'null' | ||
res += `\n${originalIndentation}` | ||
stack.pop() | ||
return `[${res}]` | ||
} | ||
let keys = Object.keys(value) | ||
if (keys.length === 0) { | ||
return '{}' | ||
} | ||
indentation += spacer | ||
const join = `,\n${indentation}` | ||
let res = '' | ||
let separator = '' | ||
if (isTypedArray(value)) { | ||
res += stringifyTypedArray(value, join) | ||
keys = keys.slice(value.length) | ||
separator = join | ||
} | ||
if (deterministic) { | ||
keys = insertSort(keys) | ||
} | ||
stack.push(value) | ||
for (const key of keys) { | ||
const tmp = stringifyIndent(key, value[key], stack, spacer, indentation) | ||
if (tmp !== undefined) { | ||
res += `${separator}"${strEscape(key)}": ${tmp}` | ||
separator = join | ||
} | ||
} | ||
if (separator !== '') { | ||
res = `\n${indentation}${res}\n${originalIndentation}` | ||
} | ||
stack.pop() | ||
return `{${res}}` | ||
} | ||
case 'number': | ||
return isFinite(value) ? String(value) : 'null' | ||
case 'boolean': | ||
return value === true ? 'true' : 'false' | ||
case 'bigint': | ||
return bigint ? String(value) : undefined | ||
} | ||
} | ||
// Simple without any options | ||
function stringifySimple (key, value, stack) { | ||
switch (typeof value) { | ||
case 'string': | ||
return `"${strEscape(value)}"` | ||
case 'object': { | ||
if (value === null) { | ||
return 'null' | ||
} | ||
if (typeof value.toJSON === 'function') { | ||
value = value.toJSON(key) | ||
// Prevent calling `toJSON` again | ||
if (typeof value !== 'object') { | ||
return stringifySimple(key, value, stack) | ||
} | ||
if (value === null) { | ||
return 'null' | ||
} | ||
} | ||
if (stack.indexOf(value) !== -1) { | ||
return circularValue | ||
} | ||
let res = '' | ||
if (Array.isArray(value)) { | ||
if (value.length === 0) { | ||
return '[]' | ||
} | ||
stack.push(value) | ||
let i = 0 | ||
for (; i < value.length - 1; i++) { | ||
const tmp = stringifySimple(i, value[i], stack) | ||
res += tmp !== undefined ? tmp : 'null' | ||
res += ',' | ||
} | ||
const tmp = stringifySimple(i, value[i], stack) | ||
res += tmp !== undefined ? tmp : 'null' | ||
stack.pop() | ||
return `[${res}]` | ||
} | ||
let keys = Object.keys(value) | ||
if (keys.length === 0) { | ||
return '{}' | ||
} | ||
let separator = '' | ||
if (isTypedArray(value)) { | ||
res += stringifyTypedArray(value, ',') | ||
keys = keys.slice(value.length) | ||
} | ||
if (deterministic) { | ||
keys = insertSort(keys) | ||
} | ||
stack.push(value) | ||
for (const key of keys) { | ||
const tmp = stringifySimple(key, value[key], stack) | ||
if (tmp !== undefined) { | ||
res += `${separator}"${strEscape(key)}":${tmp}` | ||
separator = ',' | ||
} | ||
} | ||
stack.pop() | ||
return `{${res}}` | ||
} | ||
case 'number': | ||
return isFinite(value) ? String(value) : 'null' | ||
case 'boolean': | ||
return value === true ? 'true' : 'false' | ||
case 'bigint': | ||
return bigint ? String(value) : undefined | ||
} | ||
} | ||
function stringify (value, replacer, space) { | ||
if (arguments.length > 1) { | ||
let spacer = '' | ||
if (typeof space === 'number') { | ||
spacer = ' '.repeat(space) | ||
} else if (typeof space === 'string') { | ||
spacer = space | ||
} | ||
if (replacer != null) { | ||
if (typeof replacer === 'function') { | ||
return stringifyFullFn('', { '': value }, [], replacer, spacer, '') | ||
} | ||
if (Array.isArray(replacer)) { | ||
return stringifyFullArr('', value, [], replacer, spacer, '') | ||
} | ||
} | ||
if (spacer !== '') { | ||
return stringifyIndent('', value, [], spacer, '') | ||
} | ||
} | ||
return stringifySimple('', value, []) | ||
} | ||
return stringify | ||
} |
{ | ||
"name": "safe-stable-stringify", | ||
"version": "1.1.1", | ||
"version": "2.0.0", | ||
"description": "Deterministic and safely JSON.stringify to quickly serialize JavaScript objects", | ||
"exports": { | ||
"require": "./index.js", | ||
"import": "./esm/wrapper.js" | ||
}, | ||
"keywords": [ | ||
@@ -40,4 +44,4 @@ "stable", | ||
"json-stringify-safe": "^5.0.1", | ||
"standard": "^14.3.3", | ||
"tap": "^14.10.7" | ||
"standard": "^15.0.0", | ||
"tap": "^15.0.9" | ||
}, | ||
@@ -51,4 +55,3 @@ "repository": { | ||
}, | ||
"homepage": "https://github.com/BridgeAR/safe-stable-stringify#readme", | ||
"dependencies": {} | ||
"homepage": "https://github.com/BridgeAR/safe-stable-stringify#readme" | ||
} |
# safe-stable-stringify | ||
[![Greenkeeper badge](https://badges.greenkeeper.io/BridgeAR/safe-stable-stringify.svg)](https://greenkeeper.io/) | ||
Safe, deterministic and fast serialization alternative to [JSON.stringify][]. Zero dependencies. ESM and CJS. 100% coverage. | ||
Safe, deterministic and fast serialization alternative to [JSON.stringify][]. | ||
Gracefully handles circular structures and bigint instead of throwing. | ||
Gracefully handles circular structures instead of throwing. | ||
Optional custom circular values and deterministic behavior. | ||
## Usage | ||
## stringify(value[, replacer[, space]]) | ||
The same as [JSON.stringify][]. | ||
`stringify(value[, replacer[, space]])` | ||
* `value` {any} | ||
* `replacer` {string[]|function|null} | ||
* `space` {number|string} | ||
* Returns: {string} | ||
```js | ||
const stringify = require('safe-stable-stringify') | ||
const o = { b: 1, a: 0 } | ||
o.o = o | ||
console.log(stringify(o)) | ||
// '{"a":0,"b":1,"o":"[Circular]"}' | ||
console.log(JSON.stringify(o)) | ||
const bigint = { a: 0, c: 2n, b: 1 } | ||
stringify(bigint) | ||
// '{"a":0,"b":1,"c":2}' | ||
JSON.stringify(bigint) | ||
// TypeError: Do not know how to serialize a BigInt | ||
const circular = { b: 1, a: 0 } | ||
circular.circular = circular | ||
stringify(circular) | ||
// '{"a":0,"b":1,"circular":"[Circular]"}' | ||
JSON.stringify(circular) | ||
// TypeError: Converting circular structure to JSON | ||
function replacer(key, value) { | ||
console.log('Key:', JSON.stringify(key)) | ||
// Remove the circular structure | ||
if (key === 'o') { | ||
return | ||
} | ||
return value | ||
} | ||
const serialized = stringify(o, replacer, 2) | ||
// Key: "" | ||
// Key: "a" | ||
// Key: "b" | ||
// Key: "o" | ||
console.log(serialized) | ||
stringify(circular, ['a', 'b'], 2) | ||
// { | ||
@@ -45,6 +43,47 @@ // "a": 0, | ||
## stringify.configure(options) | ||
* `bigint` {boolean} If `true`, bigint values are converted to a number. Otherwise | ||
they are ignored. **Default:** `true`. | ||
* `circularValue` {string|null} Define the value for circular references. **Default:** `[Circular]`. | ||
* `deterministic` {boolean} If `true`, guarantee a deterministic key order | ||
instead of relying on the insertion order. **Default:** `true`. | ||
* Returns: {function} A stringify function with the options applied. | ||
```js | ||
import { configure } from 'safe-stable-stringify' | ||
const stringify = configure({ | ||
bigint: true, | ||
circularValue: 'Magic circle!', | ||
deterministic: false, | ||
}) | ||
const circular = { | ||
bigint: 999_999_999_999_999_999n, | ||
typed: new Uint8Array(3), | ||
deterministic: "I don't think so", | ||
} | ||
circular.circular = circular | ||
const stringified = stringify(circular, null, 4) | ||
console.log(stringified) | ||
// { | ||
// "bigint": 999999999999999999, | ||
// "typed": { | ||
// "0": 0, | ||
// "1": 0, | ||
// "2": 0 | ||
// }, | ||
// "deterministic": "I don't think so", | ||
// "circular": "Magic circle!" | ||
// } | ||
``` | ||
## Differences to JSON.stringify | ||
1. replace circular structures with the string `[Circular]` | ||
1. sorted keys instead of using the insertion order | ||
1. Replace circular structures with the string `[Circular]` (the value may be changed). | ||
1. Sorted keys instead of using the insertion order (it is possible to deactivate this). | ||
1. BigInt values are stringified as regular number instead of throwing a TypeError. | ||
@@ -58,3 +97,3 @@ Those are the only differences to the real JSON.stringify. This is a side effect | ||
Currently this is by far the fastest known stable stringify implementation. | ||
This is especially important for big objects. | ||
This is especially important for big objects and TypedArrays. | ||
@@ -98,3 +137,3 @@ (Lenovo T450s with a i7-5600U CPU using Node.js 8.9.4) | ||
Sponsored by [nearForm](http://nearform.com) | ||
Sponsored by [MaibornWolff](https://www.maibornwolff.de/) and [nearForm](http://nearform.com) | ||
@@ -101,0 +140,0 @@ ## License |
265
test.js
@@ -12,3 +12,3 @@ const { test } = require('tap') | ||
const actual = stringify(fixture) | ||
assert.is(actual, expected) | ||
assert.equal(actual, expected) | ||
assert.end() | ||
@@ -24,3 +24,3 @@ }) | ||
const actual = stringify(fixture) | ||
assert.is(actual, expected) | ||
assert.equal(actual, expected) | ||
assert.end() | ||
@@ -39,3 +39,3 @@ }) | ||
const actual = stringify(fixture) | ||
assert.is(actual, expected) | ||
assert.equal(actual, expected) | ||
assert.end() | ||
@@ -54,3 +54,3 @@ }) | ||
const actual = stringify(fixture) | ||
assert.is(actual, expected) | ||
assert.equal(actual, expected) | ||
assert.end() | ||
@@ -66,3 +66,3 @@ }) | ||
const actual = stringify(fixture) | ||
assert.is(actual, expected) | ||
assert.equal(actual, expected) | ||
assert.end() | ||
@@ -87,3 +87,3 @@ }) | ||
const actual = stringify(fixture) | ||
assert.is(actual, expected) | ||
assert.equal(actual, expected) | ||
assert.end() | ||
@@ -97,3 +97,3 @@ }) | ||
const actual = stringify(fixture) | ||
assert.is(actual, expected) | ||
assert.equal(actual, expected) | ||
assert.end() | ||
@@ -113,3 +113,3 @@ }) | ||
const actual = stringify(fixture) | ||
assert.is(actual, expected) | ||
assert.equal(actual, expected) | ||
assert.end() | ||
@@ -126,3 +126,3 @@ }) | ||
const actual = stringify(fixture) | ||
assert.is(actual, expected) | ||
assert.equal(actual, expected) | ||
assert.end() | ||
@@ -136,3 +136,3 @@ }) | ||
const actual = stringify(fixture) | ||
assert.is(actual, expected) | ||
assert.equal(actual, expected) | ||
assert.end() | ||
@@ -159,6 +159,6 @@ }) | ||
const actual = stringify(fixture) | ||
assert.is(actual, expected) | ||
assert.equal(actual, expected) | ||
// check if the fixture has not been modified | ||
assert.deepEqual(fixture, cloned) | ||
assert.same(fixture, cloned) | ||
assert.end() | ||
@@ -183,4 +183,4 @@ }) | ||
// Making sure our original tests work | ||
assert.deepEqual(parentObject.childObject.parentObject, parentObject) | ||
assert.deepEqual(otherParentObject.otherChildObject.otherParentObject, otherParentObject) | ||
assert.same(parentObject.childObject.parentObject, parentObject) | ||
assert.same(otherParentObject.otherChildObject.otherParentObject, otherParentObject) | ||
@@ -192,4 +192,4 @@ // Should both be idempotent | ||
// Therefore the following assertion should be `true` | ||
assert.deepEqual(parentObject.childObject.parentObject, parentObject) | ||
assert.deepEqual(otherParentObject.otherChildObject.otherParentObject, otherParentObject) | ||
assert.same(parentObject.childObject.parentObject, parentObject) | ||
assert.same(otherParentObject.otherChildObject.otherParentObject, otherParentObject) | ||
@@ -202,3 +202,3 @@ assert.end() | ||
const actual = stringify(null) | ||
assert.is(actual, expected) | ||
assert.equal(actual, expected) | ||
assert.end() | ||
@@ -211,3 +211,3 @@ }) | ||
const actual = stringify(obj) | ||
assert.is(actual, expected) | ||
assert.equal(actual, expected) | ||
assert.end() | ||
@@ -220,3 +220,3 @@ }) | ||
const actual = stringify(obj) | ||
assert.is(actual, expected) | ||
assert.equal(actual, expected) | ||
assert.end() | ||
@@ -262,6 +262,16 @@ }) | ||
const actual = stringify(o) | ||
assert.is(actual, expected) | ||
assert.equal(actual, expected) | ||
assert.end() | ||
}) | ||
test('invalid replacer being ignored', function (assert) { | ||
const obj = { a: true } | ||
const actual = stringify(obj, 'invalidReplacer') | ||
const expected = stringify(obj) | ||
assert.equal(actual, expected) | ||
assert.end() | ||
}) | ||
test('replacer removing elements', function (assert) { | ||
@@ -272,10 +282,10 @@ const replacer = function (k, v) { | ||
} | ||
const obj = { f: null, remove: true } | ||
const obj = { f: null, remove: true, typed: new Int32Array(1) } | ||
const expected = JSON.stringify(obj, replacer) | ||
let actual = stringify(obj, replacer) | ||
assert.is(actual, expected) | ||
assert.equal(actual, expected) | ||
obj.obj = obj | ||
actual = stringify(obj, replacer) | ||
assert.is(actual, '{"f":null,"obj":"[Circular]"}') | ||
assert.equal(actual, '{"f":null,"obj":"[Circular]","typed":{"0":0}}') | ||
@@ -293,3 +303,3 @@ assert.end() | ||
const actual = stringify(obj, replacer, 2) | ||
assert.is(actual, expected) | ||
assert.equal(actual, expected) | ||
assert.end() | ||
@@ -306,7 +316,7 @@ }) | ||
let actual = stringify(obj, replacer) | ||
assert.is(actual, expected) | ||
assert.equal(actual, expected) | ||
expected = JSON.stringify({ toJSON () { return obj } }, replacer) | ||
actual = stringify({ toJSON () { return obj } }, replacer) | ||
assert.is(actual, expected) | ||
assert.equal(actual, expected) | ||
@@ -324,3 +334,3 @@ assert.end() | ||
const actual = stringify(obj, replacer, 2) | ||
assert.is(actual, expected) | ||
assert.equal(actual, expected) | ||
assert.end() | ||
@@ -335,3 +345,3 @@ }) | ||
let actual = stringify(obj, replacer) | ||
assert.is(actual, expected) | ||
assert.equal(actual, expected) | ||
@@ -341,3 +351,3 @@ obj.f = obj | ||
actual = stringify({ toJSON () { return obj } }, replacer) | ||
assert.is(actual, expected.replace('null', '"[Circular]"')) | ||
assert.equal(actual, expected.replace('null', '"[Circular]"')) | ||
@@ -353,3 +363,3 @@ assert.end() | ||
const actual = stringify(obj, replacer) | ||
assert.is(actual, expected) | ||
assert.equal(actual, expected) | ||
@@ -365,3 +375,3 @@ assert.end() | ||
const actual = stringify(obj, replacer, 2) | ||
assert.is(actual, expected) | ||
assert.equal(actual, expected) | ||
assert.end() | ||
@@ -374,3 +384,3 @@ }) | ||
const actual = stringify(obj, null, 0) | ||
assert.is(actual, expected) | ||
assert.equal(actual, expected) | ||
assert.end() | ||
@@ -386,3 +396,3 @@ }) | ||
const actual = stringify(obj, replacer, ' ') | ||
assert.is(actual, expected) | ||
assert.equal(actual, expected) | ||
assert.end() | ||
@@ -396,3 +406,3 @@ }) | ||
const actual = stringify(obj, replacer, ' ') | ||
assert.is(actual, expected) | ||
assert.equal(actual, expected) | ||
assert.end() | ||
@@ -405,3 +415,3 @@ }) | ||
const actual = stringify(obj, undefined, 3) | ||
assert.is(actual, expected) | ||
assert.equal(actual, expected) | ||
assert.end() | ||
@@ -414,3 +424,3 @@ }) | ||
const actual = stringify(obj, undefined, 3) | ||
assert.is(actual, expected) | ||
assert.equal(actual, expected) | ||
assert.end() | ||
@@ -423,3 +433,3 @@ }) | ||
const actual = stringify(obj, (_, v) => v, 3) | ||
assert.is(actual, expected) | ||
assert.equal(actual, expected) | ||
assert.end() | ||
@@ -432,3 +442,3 @@ }) | ||
const actual = stringify(obj, (_, v) => v) | ||
assert.is(actual, expected) | ||
assert.equal(actual, expected) | ||
assert.end() | ||
@@ -441,3 +451,3 @@ }) | ||
const actual = stringify(obj, [false], 3) | ||
assert.is(actual, expected) | ||
assert.equal(actual, expected) | ||
assert.end() | ||
@@ -450,3 +460,3 @@ }) | ||
const actual = stringify(obj, [2]) | ||
assert.is(actual, expected) | ||
assert.equal(actual, expected) | ||
assert.end() | ||
@@ -459,3 +469,3 @@ }) | ||
const actual = stringify(obj, null, 5) | ||
assert.is(actual, expected) | ||
assert.equal(actual, expected) | ||
assert.end() | ||
@@ -469,3 +479,3 @@ }) | ||
let actual = stringify(obj) | ||
assert.is(actual, expected) | ||
assert.equal(actual, expected) | ||
@@ -476,3 +486,3 @@ obj = { b: 'hello', a: undefined, c: 1 } | ||
actual = stringify(obj) | ||
assert.is(actual, expected) | ||
assert.equal(actual, expected) | ||
@@ -487,3 +497,3 @@ assert.end() | ||
let actual = stringify(obj, null, 2) | ||
assert.is(actual, expected) | ||
assert.equal(actual, expected) | ||
@@ -494,3 +504,3 @@ obj = { b: 'hello', a: undefined, c: 1 } | ||
actual = stringify(obj) | ||
assert.is(actual, expected) | ||
assert.equal(actual, expected) | ||
@@ -500,2 +510,153 @@ assert.end() | ||
test('bigint option', function (assert) { | ||
const stringifyNoBigInt = stringify.configure({ bigint: false }) | ||
const stringifyBigInt = stringify.configure({ bigint: true }) | ||
const obj = { a: 1n } | ||
const actualBigInt = stringifyBigInt(obj, null, 1) | ||
const actualNoBigInt = stringifyNoBigInt(obj, null, 1) | ||
const actualDefault = stringify(obj, null, 1) | ||
const expectedBigInt = '{\n "a": 1\n}' | ||
const expectedNoBigInt = '{}' | ||
assert.equal(actualNoBigInt, expectedNoBigInt) | ||
assert.throws(() => JSON.stringify(obj, null, 1), TypeError) | ||
assert.equal(actualBigInt, expectedBigInt) | ||
assert.equal(actualDefault, expectedBigInt) | ||
assert.throws(() => stringify.configure({ bigint: null }), /bigint/) | ||
assert.end() | ||
}) | ||
test('bigint option with replacer', function (assert) { | ||
const stringifyBigInt = stringify.configure({ bigint: true }) | ||
const obj = { a: new BigUint64Array([1n]), 0: 1n } | ||
const actualArrayReplacer = stringifyBigInt(obj, ['0', 'a']) | ||
const actualFnReplacer = stringifyBigInt(obj, (k, v) => v) | ||
const expected = '{"0":1,"a":{"0":1}}' | ||
assert.equal(actualArrayReplacer, expected) | ||
assert.equal(actualFnReplacer, expected) | ||
assert.end() | ||
}) | ||
test('bigint and typed array with indentation', function (assert) { | ||
const obj = { a: 1n, t: new Int8Array(1) } | ||
const expected = '{\n "a": 1,\n "t": {\n "0": 0\n }\n}' | ||
const actual = stringify(obj, null, 1) | ||
assert.equal(actual, expected) | ||
assert.end() | ||
}) | ||
test('bigint and typed array without indentation', function (assert) { | ||
const obj = { a: 1n, t: new Int8Array(1) } | ||
const expected = '{"a":1,"t":{"0":0}}' | ||
const actual = stringify(obj, null, 0) | ||
assert.equal(actual, expected) | ||
assert.end() | ||
}) | ||
test('no bigint without indentation', function (assert) { | ||
const stringifyNoBigInt = stringify.configure({ bigint: false }) | ||
const obj = { a: 1n, t: new Int8Array(1) } | ||
const expected = '{"t":{"0":0}}' | ||
const actual = stringifyNoBigInt(obj, null, 0) | ||
assert.equal(actual, expected) | ||
assert.end() | ||
}) | ||
test('circular value option', function (assert) { | ||
let stringifyCircularValue = stringify.configure({ circularValue: 'YEAH!!!' }) | ||
const obj = {} | ||
obj.circular = obj | ||
const expected = '{"circular":"YEAH!!!"}' | ||
const actual = stringifyCircularValue(obj) | ||
assert.equal(actual, expected) | ||
assert.equal(actual, expected) | ||
assert.equal(stringify(obj), '{"circular":"[Circular]"}') | ||
assert.throws(() => stringify.configure({ circularValue: { objects: 'are not allowed' } }), /circularValue/) | ||
stringifyCircularValue = stringify.configure({ circularValue: null }) | ||
assert.equal(stringifyCircularValue(obj), '{"circular":null}') | ||
assert.end() | ||
}) | ||
test('non-deterministic', function (assert) { | ||
const stringifyNonDeterministic = stringify.configure({ deterministic: false }) | ||
const obj = { b: true, a: false } | ||
const expected = JSON.stringify(obj) | ||
const actual = stringifyNonDeterministic(obj) | ||
assert.equal(actual, expected) | ||
assert.throws(() => stringify.configure({ deterministic: 1 }), /deterministic/) | ||
assert.end() | ||
}) | ||
test('non-deterministic with replacer', function (assert) { | ||
const stringifyNonDeterministic = stringify.configure({ deterministic: false, bigint: false }) | ||
const obj = { b: true, a: 5n, c: Infinity, d: 4, e: [Symbol('null'), 5, Symbol('null')] } | ||
const keys = Object.keys(obj) | ||
const expected = stringify(obj, ['b', 'c', 'd', 'e']) | ||
let actual = stringifyNonDeterministic(obj, keys) | ||
assert.equal(actual, expected) | ||
actual = stringifyNonDeterministic(obj, (k, v) => v) | ||
assert.equal(actual, expected) | ||
assert.end() | ||
}) | ||
test('non-deterministic with indentation', function (assert) { | ||
const stringifyNonDeterministic = stringify.configure({ deterministic: false, bigint: false }) | ||
const obj = { b: true, a: 5, c: Infinity, d: false, e: [Symbol('null'), 5, Symbol('null')] } | ||
const expected = JSON.stringify(obj, null, 1) | ||
const actual = stringifyNonDeterministic(obj, null, 1) | ||
assert.equal(actual, expected) | ||
assert.end() | ||
}) | ||
test('check typed arrays', function (assert) { | ||
const obj = [null, null, new Float32Array(99), Infinity, Symbol('null'), true, false, [], {}, Symbol('null')] | ||
const expected = JSON.stringify(obj) | ||
const actual = stringify(obj) | ||
assert.equal(actual, expected) | ||
assert.end() | ||
}) | ||
test('check small typed arrays with extra properties', function (assert) { | ||
const obj = new Uint8Array(0) | ||
obj.foo = true | ||
const expected = JSON.stringify(obj) | ||
const actual = stringify(obj) | ||
assert.equal(actual, expected) | ||
assert.end() | ||
}) | ||
test('trigger sorting fast path for objects with lots of properties', function (assert) { | ||
const obj = {} | ||
for (let i = 0; i < 1e4; i++) { | ||
obj[`a${i}`] = i | ||
} | ||
const start = Date.now() | ||
stringify(obj) | ||
assert.ok(Date.now() - start < 100) | ||
assert.end() | ||
}) | ||
test('indent properly; regression test for issue #16', function (assert) { | ||
@@ -522,15 +683,15 @@ const o = { | ||
assert.is( | ||
assert.equal( | ||
stringify(o, null, 2), | ||
indentedJSON | ||
) | ||
assert.is( | ||
assert.equal( | ||
stringify(o, arrayReplacer, 2), | ||
indentedJSONArrayReplacer | ||
) | ||
assert.is( | ||
assert.equal( | ||
stringify(o, [], 2), | ||
indentedJSONArrayEmpty | ||
) | ||
assert.is( | ||
assert.equal( | ||
stringify(o, (k, v) => v, 2), | ||
@@ -545,11 +706,11 @@ indentedJSONReplacer | ||
assert.is( | ||
assert.equal( | ||
stringify(o, arrayReplacer, 2), | ||
indentedJSONArrayReplacer.replace(circularIdentifier, circularReplacement) | ||
) | ||
assert.is( | ||
assert.equal( | ||
stringify(o, null, 2), | ||
indentedJSON.replace(circularIdentifier, circularReplacement) | ||
) | ||
assert.is( | ||
assert.equal( | ||
stringify(o, (k, v) => v, 2), | ||
@@ -556,0 +717,0 @@ indentedJSONReplacer.replace(circularIdentifier, circularReplacement) |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
56011
13
1423
145