fast-safe-stringify
Advanced tools
Comparing version 2.0.6 to 2.0.7
@@ -11,2 +11,5 @@ const Benchmark = require('benchmark') | ||
circ.o = { obj: circ, array } | ||
const circGetters = JSON.parse(JSON.stringify(obj)) | ||
Object.assign(circGetters, { get o () { return { obj: circGetters, array } } }) | ||
const deep = require('./package.json') | ||
@@ -24,40 +27,103 @@ deep.deep = JSON.parse(JSON.stringify(deep)) | ||
suite.add('util.inspect: simple object', function () { | ||
inspect(obj) | ||
const deepCircGetters = JSON.parse(JSON.stringify(deep)) | ||
for (let i = 0; i < 10; i++) { | ||
deepCircGetters[i.toString()] = { | ||
deep: { | ||
deep: { | ||
get circ () { return deep.deep }, | ||
deep: { get circ () { return deep.deep.deep } } | ||
}, | ||
get circ () { return deep } | ||
}, | ||
get array () { return array } | ||
} | ||
} | ||
const deepCircNonCongifurableGetters = JSON.parse(JSON.stringify(deep)) | ||
Object.defineProperty(deepCircNonCongifurableGetters.deep.deep.deep, 'circ', { | ||
get: () => deepCircNonCongifurableGetters, | ||
enumerable: true, | ||
configurable: false | ||
}) | ||
suite.add('util.inspect: circular ', function () { | ||
inspect(circ) | ||
Object.defineProperty(deepCircNonCongifurableGetters.deep.deep, 'circ', { | ||
get: () => deepCircNonCongifurableGetters, | ||
enumerable: true, | ||
configurable: false | ||
}) | ||
suite.add('util.inspect: deep ', function () { | ||
inspect(deep) | ||
Object.defineProperty(deepCircNonCongifurableGetters.deep, 'circ', { | ||
get: () => deepCircNonCongifurableGetters, | ||
enumerable: true, | ||
configurable: false | ||
}) | ||
suite.add('util.inspect: deep circular', function () { | ||
inspect(deepCirc) | ||
Object.defineProperty(deepCircNonCongifurableGetters, 'array', { | ||
get: () => array, | ||
enumerable: true, | ||
configurable: false | ||
}) | ||
suite.add('\njson-stringify-safe: simple object', function () { | ||
suite.add('util.inspect: simple object ', function () { | ||
inspect(obj, { showHidden: false, depth: null }) | ||
}) | ||
suite.add('util.inspect: circular ', function () { | ||
inspect(circ, { showHidden: false, depth: null }) | ||
}) | ||
suite.add('util.inspect: circular getters ', function () { | ||
inspect(circGetters, { showHidden: false, depth: null }) | ||
}) | ||
suite.add('util.inspect: deep ', function () { | ||
inspect(deep, { showHidden: false, depth: null }) | ||
}) | ||
suite.add('util.inspect: deep circular ', function () { | ||
inspect(deepCirc, { showHidden: false, depth: null }) | ||
}) | ||
suite.add('util.inspect: large deep circular getters ', function () { | ||
inspect(deepCircGetters, { showHidden: false, depth: null }) | ||
}) | ||
suite.add('util.inspect: deep non-conf circular getters', function () { | ||
inspect(deepCircNonCongifurableGetters, { showHidden: false, depth: null }) | ||
}) | ||
suite.add('\njson-stringify-safe: simple object ', function () { | ||
jsonStringifySafe(obj) | ||
}) | ||
suite.add('json-stringify-safe: circular ', function () { | ||
suite.add('json-stringify-safe: circular ', function () { | ||
jsonStringifySafe(circ) | ||
}) | ||
suite.add('json-stringify-safe: deep ', function () { | ||
suite.add('json-stringify-safe: circular getters ', function () { | ||
jsonStringifySafe(circGetters) | ||
}) | ||
suite.add('json-stringify-safe: deep ', function () { | ||
jsonStringifySafe(deep) | ||
}) | ||
suite.add('json-stringify-safe: deep circular', function () { | ||
suite.add('json-stringify-safe: deep circular ', function () { | ||
jsonStringifySafe(deepCirc) | ||
}) | ||
suite.add('json-stringify-safe: large deep circular getters ', function () { | ||
jsonStringifySafe(deepCircGetters) | ||
}) | ||
suite.add('json-stringify-safe: deep non-conf circular getters', function () { | ||
jsonStringifySafe(deepCircNonCongifurableGetters) | ||
}) | ||
suite.add('\nfast-safe-stringify: simple object', function () { | ||
suite.add('\nfast-safe-stringify: simple object ', function () { | ||
fastSafeStringify(obj) | ||
}) | ||
suite.add('fast-safe-stringify: circular ', function () { | ||
suite.add('fast-safe-stringify: circular ', function () { | ||
fastSafeStringify(circ) | ||
}) | ||
suite.add('fast-safe-stringify: deep ', function () { | ||
suite.add('fast-safe-stringify: circular getters ', function () { | ||
fastSafeStringify(circGetters) | ||
}) | ||
suite.add('fast-safe-stringify: deep ', function () { | ||
fastSafeStringify(deep) | ||
}) | ||
suite.add('fast-safe-stringify: deep circular', function () { | ||
suite.add('fast-safe-stringify: deep circular ', function () { | ||
fastSafeStringify(deepCirc) | ||
}) | ||
suite.add('fast-safe-stringify: large deep circular getters ', function () { | ||
fastSafeStringify(deepCircGetters) | ||
}) | ||
suite.add('fast-safe-stringify: deep non-conf circular getters', function () { | ||
fastSafeStringify(deepCircNonCongifurableGetters) | ||
}) | ||
@@ -64,0 +130,0 @@ // add listeners |
74
index.js
@@ -7,2 +7,3 @@ module.exports = stringify | ||
var arr = [] | ||
var replacerStack = [] | ||
@@ -12,6 +13,15 @@ // Regular stringify | ||
decirc(obj, '', [], undefined) | ||
var res = JSON.stringify(obj, replacer, spacer) | ||
var res | ||
if (replacerStack.length === 0) { | ||
res = JSON.stringify(obj, replacer, spacer) | ||
} else { | ||
res = JSON.stringify(obj, replaceGetterValues(replacer), spacer) | ||
} | ||
while (arr.length !== 0) { | ||
var part = arr.pop() | ||
part[0][part[1]] = part[2] | ||
if (part.length === 4) { | ||
Object.defineProperty(part[0], part[1], part[3]) | ||
} else { | ||
part[0][part[1]] = part[2] | ||
} | ||
} | ||
@@ -25,4 +35,14 @@ return res | ||
if (stack[i] === val) { | ||
parent[k] = '[Circular]' | ||
arr.push([parent, k, val]) | ||
var propertyDescriptor = Object.getOwnPropertyDescriptor(parent, k) | ||
if (propertyDescriptor.get !== undefined) { | ||
if (propertyDescriptor.configurable) { | ||
Object.defineProperty(parent, k, { value: '[Circular]' }) | ||
arr.push([parent, k, val, propertyDescriptor]) | ||
} else { | ||
replacerStack.push([val, k]) | ||
} | ||
} else { | ||
parent[k] = '[Circular]' | ||
arr.push([parent, k, val]) | ||
} | ||
return | ||
@@ -61,6 +81,15 @@ } | ||
var tmp = deterministicDecirc(obj, '', [], undefined) || obj | ||
var res = JSON.stringify(tmp, replacer, spacer) | ||
var res | ||
if (replacerStack.length === 0) { | ||
res = JSON.stringify(tmp, replacer, spacer) | ||
} else { | ||
res = JSON.stringify(tmp, replaceGetterValues(replacer), spacer) | ||
} | ||
while (arr.length !== 0) { | ||
var part = arr.pop() | ||
part[0][part[1]] = part[2] | ||
if (part.length === 4) { | ||
Object.defineProperty(part[0], part[1], part[3]) | ||
} else { | ||
part[0][part[1]] = part[2] | ||
} | ||
} | ||
@@ -75,4 +104,14 @@ return res | ||
if (stack[i] === val) { | ||
parent[k] = '[Circular]' | ||
arr.push([parent, k, val]) | ||
var propertyDescriptor = Object.getOwnPropertyDescriptor(parent, k) | ||
if (propertyDescriptor.get !== undefined) { | ||
if (propertyDescriptor.configurable) { | ||
Object.defineProperty(parent, k, { value: '[Circular]' }) | ||
arr.push([parent, k, val, propertyDescriptor]) | ||
} else { | ||
replacerStack.push([val, k]) | ||
} | ||
} else { | ||
parent[k] = '[Circular]' | ||
arr.push([parent, k, val]) | ||
} | ||
return | ||
@@ -109,1 +148,20 @@ } | ||
} | ||
// wraps replacer function to handle values we couldn't replace | ||
// and mark them as [Circular] | ||
function replaceGetterValues (replacer) { | ||
replacer = replacer !== undefined ? replacer : function (k, v) { return v } | ||
return function (key, val) { | ||
if (replacerStack.length > 0) { | ||
for (var i = 0; i < replacerStack.length; i++) { | ||
var part = replacerStack[i] | ||
if (part[1] === key && part[0] === val) { | ||
val = '[Circular]' | ||
replacerStack.splice(i, 1) | ||
break | ||
} | ||
} | ||
} | ||
return replacer.call(this, key, val) | ||
} | ||
} |
{ | ||
"name": "fast-safe-stringify", | ||
"version": "2.0.6", | ||
"version": "2.0.7", | ||
"description": "Safely and quickly serialize JavaScript objects", | ||
@@ -15,3 +15,3 @@ "keywords": [ | ||
"scripts": { | ||
"test": "standard && tap test.js test-stable.js", | ||
"test": "standard && tap --no-esm test.js test-stable.js", | ||
"benchmark": "node benchmark.js" | ||
@@ -18,0 +18,0 @@ }, |
@@ -17,2 +17,18 @@ const test = require('tap').test | ||
test('circular getter reference to root', function (assert) { | ||
const fixture = { | ||
name: 'Tywin Lannister', | ||
get circle () { | ||
return fixture | ||
} | ||
} | ||
const expected = s( | ||
{ circle: '[Circular]', name: 'Tywin Lannister' } | ||
) | ||
const actual = fss(fixture) | ||
assert.is(actual, expected) | ||
assert.end() | ||
}) | ||
test('nested circular reference to root', function (assert) { | ||
@@ -248,1 +264,50 @@ const fixture = { name: 'Tywin Lannister' } | ||
}) | ||
test('circular getters are restored when stringified', function (assert) { | ||
const fixture = { | ||
name: 'Tywin Lannister', | ||
get circle () { | ||
return fixture | ||
} | ||
} | ||
fss(fixture) | ||
assert.is(fixture.circle, fixture) | ||
assert.end() | ||
}) | ||
test('non-configurable circular getters use a replacer instead of markers', function (assert) { | ||
const fixture = { name: 'Tywin Lannister' } | ||
Object.defineProperty(fixture, 'circle', { | ||
configurable: false, | ||
get: function () { return fixture }, | ||
enumerable: true | ||
}) | ||
fss(fixture) | ||
assert.is(fixture.circle, fixture) | ||
assert.end() | ||
}) | ||
test('getter child circular reference', function (assert) { | ||
const fixture = { | ||
name: 'Tywin Lannister', | ||
child: { | ||
name: 'Tyrion Lannister', | ||
get dinklage () { return fixture.child } | ||
}, | ||
get self () { return fixture } | ||
} | ||
const expected = s({ | ||
child: { | ||
dinklage: '[Circular]', name: 'Tyrion Lannister' | ||
}, | ||
name: 'Tywin Lannister', | ||
self: '[Circular]' | ||
}) | ||
const actual = fss(fixture) | ||
assert.is(actual, expected) | ||
assert.end() | ||
}) |
64
test.js
@@ -17,2 +17,17 @@ const test = require('tap').test | ||
test('circular getter reference to root', function (assert) { | ||
const fixture = { | ||
name: 'Tywin Lannister', | ||
get circle () { | ||
return fixture | ||
} | ||
} | ||
const expected = s( | ||
{ name: 'Tywin Lannister', circle: '[Circular]' } | ||
) | ||
const actual = fss(fixture) | ||
assert.is(actual, expected) | ||
assert.end() | ||
}) | ||
test('nested circular reference to root', function (assert) { | ||
@@ -242,1 +257,50 @@ const fixture = { name: 'Tywin Lannister' } | ||
}) | ||
test('circular getters are restored when stringified', function (assert) { | ||
const fixture = { | ||
name: 'Tywin Lannister', | ||
get circle () { | ||
return fixture | ||
} | ||
} | ||
fss(fixture) | ||
assert.is(fixture.circle, fixture) | ||
assert.end() | ||
}) | ||
test('non-configurable circular getters use a replacer instead of markers', function (assert) { | ||
const fixture = { name: 'Tywin Lannister' } | ||
Object.defineProperty(fixture, 'circle', { | ||
configurable: false, | ||
get: function () { return fixture }, | ||
enumerable: true | ||
}) | ||
fss(fixture) | ||
assert.is(fixture.circle, fixture) | ||
assert.end() | ||
}) | ||
test('getter child circular reference are replaced instead of marked', function (assert) { | ||
const fixture = { | ||
name: 'Tywin Lannister', | ||
child: { | ||
name: 'Tyrion Lannister', | ||
get dinklage () { return fixture.child } | ||
}, | ||
get self () { return fixture } | ||
} | ||
const expected = s({ | ||
name: 'Tywin Lannister', | ||
child: { | ||
name: 'Tyrion Lannister', dinklage: '[Circular]' | ||
}, | ||
self: '[Circular]' | ||
}) | ||
const actual = fss(fixture) | ||
assert.is(actual, expected) | ||
assert.end() | ||
}) |
34024
835