From 4e6d9740965ce7ee18c553f989c72e0223cb8fee Mon Sep 17 00:00:00 2001 From: Marc MacLeod Date: Tue, 10 Apr 2018 20:47:43 -0500 Subject: [PATCH 1/5] make decirc function public --- index.d.ts | 3 ++- index.js | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/index.d.ts b/index.d.ts index a842bf3..948c94b 100644 --- a/index.d.ts +++ b/index.d.ts @@ -3,6 +3,7 @@ declare function stringify(data: any): string; declare namespace stringify { export function stable(data: any): string; export function stableStringify(data: any): string; + export function decirc(val: any, k: string, stack: any[], parent?: any): void; } -export default stringify; +export default stringify; \ No newline at end of file diff --git a/index.js b/index.js index 6fe35f3..402e5fc 100644 --- a/index.js +++ b/index.js @@ -2,6 +2,7 @@ module.exports = stringify stringify.default = stringify stringify.stable = deterministicStringify stringify.stableStringify = deterministicStringify +stringify.decirc = decirc const arr = [] From b2b3b1b8c9c13599b59f19ca82787194f1f060f4 Mon Sep 17 00:00:00 2001 From: Marc MacLeod Date: Thu, 12 Apr 2018 14:51:33 -0500 Subject: [PATCH 2/5] decirc -> decycle, custom replacers - expose new decycle function instead of decirc - add replacer option to customze how circular refs are replaced - add benchmarks for decycle - add a simple test for decycle --- benchmark.js | 29 +++++++++++++++++++++++++++- index.d.ts | 2 +- index.js | 28 ++++++++++++++++++--------- test.js | 54 +++++++++++++++++++++++++++++++++++++++++++++++++++- 4 files changed, 101 insertions(+), 12 deletions(-) diff --git a/benchmark.js b/benchmark.js index 58145e3..2a07812 100644 --- a/benchmark.js +++ b/benchmark.js @@ -59,6 +59,33 @@ suite.add('fast-safe-stringify: deep circular', function () { fastSafeStringify(deepCirc) }) +suite.add('\ndecycle: simple object', function () { + fastSafeStringify.decycle(obj) +}) +suite.add('decycle: circular ', function () { + fastSafeStringify.decycle(circ) +}) +suite.add('decycle: deep ', function () { + fastSafeStringify.decycle(deep) +}) +suite.add('decycle: deep circular', function () { + fastSafeStringify.decycle(deepCirc) +}) + +const replacer = (_val, k) => k +suite.add('\ndecycle with custom replacer: simple object', function () { + fastSafeStringify.decycle(obj, replacer) +}) +suite.add('decycle with custom replacer: circular ', function () { + fastSafeStringify.decycle(circ, replacer) +}) +suite.add('decycle with custom replacer: deep ', function () { + fastSafeStringify.decycle(deep, replacer) +}) +suite.add('decycle with custom replacer: deep circular', function () { + fastSafeStringify.decycle(deepCirc, replacer) +}) + // add listeners suite.on('cycle', function (event) { console.log(String(event.target)) @@ -68,4 +95,4 @@ suite.on('complete', function () { console.log('\nFastest is ' + this.filter('fastest').map('name')) }) -suite.run({ delay: 1, minSamples: 150 }) +suite.run({ delay: 1, minSamples: 150 }) \ No newline at end of file diff --git a/index.d.ts b/index.d.ts index 948c94b..ede560e 100644 --- a/index.d.ts +++ b/index.d.ts @@ -3,7 +3,7 @@ declare function stringify(data: any): string; declare namespace stringify { export function stable(data: any): string; export function stableStringify(data: any): string; - export function decirc(val: any, k: string, stack: any[], parent?: any): void; + export function decycle(val: any, replacer?: (val: any, k: string, stack: [any, any][], parent?: any) => any | void): any; } export default stringify; \ No newline at end of file diff --git a/index.js b/index.js index 402e5fc..69b9f53 100644 --- a/index.js +++ b/index.js @@ -2,7 +2,7 @@ module.exports = stringify stringify.default = stringify stringify.stable = deterministicStringify stringify.stableStringify = deterministicStringify -stringify.decirc = decirc +stringify.decycle = decycle const arr = [] @@ -16,27 +16,37 @@ function stringify (obj, replacer, spacer) { } return res } -function decirc (val, k, stack, parent) { + +function decycle (val, replacer) { + decirc(val, '', [], undefined, replacer) + return val +} + +function defaultReplacer (val, parentKey, stack, parent) { + arr.push([parent, parentKey, val]) + return '[Circular]' +} + +function decirc (val, parentKey, stack, parent, replacer = defaultReplacer) { var i if (typeof val === 'object' && val !== null) { for (i = 0; i < stack.length; i++) { - if (stack[i] === val) { - parent[k] = '[Circular]' - arr.push([parent, k, val]) + if (stack[i][0] === val) { + parent[parentKey] = replacer(val, parentKey, stack, parent) return } } - stack.push(val) + stack.push([val, parentKey]) // Optimize for Arrays. Big arrays could kill the performance otherwise! if (Array.isArray(val)) { for (i = 0; i < val.length; i++) { - decirc(val[i], i, stack, val) + decirc(val[i], i, stack, val, replacer) } } else { const keys = Object.keys(val) for (i = 0; i < keys.length; i++) { var key = keys[i] - decirc(val[key], key, stack, val) + decirc(val[key], key, stack, val, replacer) } } stack.pop() @@ -101,4 +111,4 @@ function deterministicDecirc (val, k, stack, parent) { } stack.pop() } -} +} \ No newline at end of file diff --git a/test.js b/test.js index 0b0cdf3..384b622 100644 --- a/test.js +++ b/test.js @@ -3,6 +3,58 @@ const fss = require('./') const clone = require('clone') const s = JSON.stringify +// this custom replacer will add a json pointer $ref pointing to the referenced js object +test('decycle supports custom replacer', function (assert) { + const fixture = { + name: 'Tywin Lannister', + house2: { + inner: {} + }, + house: { + name: 'Lannister' + } + } + + fixture.house2.inner = fixture.house + fixture.house.circle = fixture.house2.inner + + const expected = { + name: 'Tywin Lannister', + house2: { + inner: { + name: 'Lannister', + circle: { + key: 'circle', + stackSize: 3, + parentName: 'Lannister', + $ref: '#/house2/inner' + } + } + }, + house: { + name: 'Lannister', + circle: { + key: 'circle', + stackSize: 3, + parentName: 'Lannister', + $ref: '#/house2/inner' + } + } + } + + const actual = fss.decycle(fixture, (val, k, stack, parent) => { + return { + key: k, + stackSize: stack.length, + parentName: parent.name, + $ref: `#${stack.map((s) => s[1]).join('/')}` + } + }) + + assert.deepEqual(actual, expected) + assert.end() +}) + test('circular reference to root', function (assert) { const fixture = { name: 'Tywin Lannister' } fixture.circle = fixture @@ -237,4 +289,4 @@ test('nested child circular reference in toJSON', function (assert) { const actual = fss(o) assert.is(actual, expected) assert.end() -}) +}) \ No newline at end of file From 823f762f200097b409d5d5bd2e65fb6a10305bb1 Mon Sep 17 00:00:00 2001 From: Marc MacLeod Date: Thu, 12 Apr 2018 14:57:49 -0500 Subject: [PATCH 3/5] add new lines to fix linting error --- benchmark.js | 2 +- index.js | 2 +- test.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/benchmark.js b/benchmark.js index 2a07812..0d3dc17 100644 --- a/benchmark.js +++ b/benchmark.js @@ -95,4 +95,4 @@ suite.on('complete', function () { console.log('\nFastest is ' + this.filter('fastest').map('name')) }) -suite.run({ delay: 1, minSamples: 150 }) \ No newline at end of file +suite.run({ delay: 1, minSamples: 150 }) diff --git a/index.js b/index.js index 69b9f53..bab8877 100644 --- a/index.js +++ b/index.js @@ -111,4 +111,4 @@ function deterministicDecirc (val, k, stack, parent) { } stack.pop() } -} \ No newline at end of file +} diff --git a/test.js b/test.js index 384b622..921213a 100644 --- a/test.js +++ b/test.js @@ -289,4 +289,4 @@ test('nested child circular reference in toJSON', function (assert) { const actual = fss(o) assert.is(actual, expected) assert.end() -}) \ No newline at end of file +}) From 1e09cc8f65453836f5473aef06111c1df4a7b04f Mon Sep 17 00:00:00 2001 From: Marc MacLeod Date: Thu, 12 Apr 2018 16:41:09 -0500 Subject: [PATCH 4/5] fix decycle test to better represent the idea of replacing circular refs with a json pointer to their target --- test.js | 127 +++++++++++++++++++++++++++++++++----------------------- 1 file changed, 75 insertions(+), 52 deletions(-) diff --git a/test.js b/test.js index 921213a..f38ebe3 100644 --- a/test.js +++ b/test.js @@ -3,58 +3,6 @@ const fss = require('./') const clone = require('clone') const s = JSON.stringify -// this custom replacer will add a json pointer $ref pointing to the referenced js object -test('decycle supports custom replacer', function (assert) { - const fixture = { - name: 'Tywin Lannister', - house2: { - inner: {} - }, - house: { - name: 'Lannister' - } - } - - fixture.house2.inner = fixture.house - fixture.house.circle = fixture.house2.inner - - const expected = { - name: 'Tywin Lannister', - house2: { - inner: { - name: 'Lannister', - circle: { - key: 'circle', - stackSize: 3, - parentName: 'Lannister', - $ref: '#/house2/inner' - } - } - }, - house: { - name: 'Lannister', - circle: { - key: 'circle', - stackSize: 3, - parentName: 'Lannister', - $ref: '#/house2/inner' - } - } - } - - const actual = fss.decycle(fixture, (val, k, stack, parent) => { - return { - key: k, - stackSize: stack.length, - parentName: parent.name, - $ref: `#${stack.map((s) => s[1]).join('/')}` - } - }) - - assert.deepEqual(actual, expected) - assert.end() -}) - test('circular reference to root', function (assert) { const fixture = { name: 'Tywin Lannister' } fixture.circle = fixture @@ -290,3 +238,78 @@ test('nested child circular reference in toJSON', function (assert) { assert.is(actual, expected) assert.end() }) + +// this custom replacer will add a json pointer $ref pointing to the referenced js object +test('decycle supports custom replacer', function (assert) { + const fixture = { + definitions: { + Customer: { + properties: { + partners: { + items: {} + } + } + }, + Partner: { + properties: { + customers: { + items: {} + } + } + } + } + } + + fixture.definitions.Customer.properties.partners.items = fixture.definitions.Partner + fixture.definitions.Partner.properties.customers.items = fixture.definitions.Customer + + const expected = { + definitions: { + Customer: { + properties: { + partners: { + items: { + properties: { + customers: { + items: { + $ref: '#/definitions/Customer' + } + } + } + } + } + } + }, + Partner: { + properties: { + customers: { + items: { + $ref: '#/definitions/Customer' + } + } + } + } + } + } + + const actual = fss.decycle(fixture, (val, k, stack, parent) => { + let $ref = '#' + + for (let i = 0; i < stack.length; i++) { + if (stack[i][1]) { + $ref += `/${stack[i][1]}` + } + + if (stack[i][0] === val) { + break + } + } + + return { + $ref + } + }) + + assert.deepEqual(actual, expected) + assert.end() +}) From 558ef4a1419a8f033d11b36ae8a714a4560994b4 Mon Sep 17 00:00:00 2001 From: Marc MacLeod Date: Wed, 18 Apr 2018 15:14:03 -0500 Subject: [PATCH 5/5] fix ie 11 --- index.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/index.js b/index.js index bab8877..532e621 100644 --- a/index.js +++ b/index.js @@ -27,7 +27,9 @@ function defaultReplacer (val, parentKey, stack, parent) { return '[Circular]' } -function decirc (val, parentKey, stack, parent, replacer = defaultReplacer) { +function decirc (val, parentKey, stack, parent, replacer) { + replacer = replacer || defaultReplacer + var i if (typeof val === 'object' && val !== null) { for (i = 0; i < stack.length; i++) {