diff --git a/index.js b/index.js index 42e1ddc..0ddfd68 100644 --- a/index.js +++ b/index.js @@ -17,8 +17,9 @@ export function flatten (target, opts) { const transformKey = opts.transformKey || keyIdentity const output = {} - function step (object, prev, currentDepth) { + function step (object, prev, currentDepth, referenceMap = new Map) { currentDepth = currentDepth || 1 + referenceMap.set(object, prev || "object-root"); Object.keys(object).forEach(function (key) { const value = object[key] const isarray = opts.safe && Array.isArray(value) @@ -35,11 +36,16 @@ export function flatten (target, opts) { if (!isarray && !isbuffer && isobject && Object.keys(value).length && (!opts.maxDepth || currentDepth < maxDepth)) { - return step(value, newKey, currentDepth + 1) + + if(referenceMap.has(value)) + throw new Error("Circular structure on key '" + key + "' (to '" + referenceMap.get(value) + "')"); + + return step(value, newKey, currentDepth + 1, referenceMap) } output[newKey] = value - }) + }); + referenceMap.delete(object); } step(target) diff --git a/test/test.js b/test/test.js index ef01c9b..2093df5 100644 --- a/test/test.js +++ b/test/test.js @@ -206,6 +206,39 @@ describe('Flatten', function () { 'hello.0500': 'darkness my old friend' }) }) + + test('Cyclic object - With depth cycle reference', function() { + let objA = { h:"hello" } + objA.propA = { A_h:"hello" } + objA.propA._propA = { _A_h:"hello" } + objA.propA._propA.cycle = objA.propA; + + try { + flatten(objA); + } catch(e) { + if(e instanceof RangeError) + throw e; + } + + }) + + test('Cyclic object - Inner reference without cycle', function() { + let objA = { h:"hello1" } + objA.propA = { A_h:"hello2" } + objA.propB = { B_h:"hello3" } + objA.propA._propA = { _A_h:"hello4" } + objA.propA._propA.no_cycle = objA.propB; + + assert.deepStrictEqual(flatten(objA),{ + "h":"hello1", + "propA.A_h":"hello2", + "propA._propA._A_h":"hello4", + "propA._propA.no_cycle.B_h":"hello3", + "propB.B_h":"hello3", + }) + }) + + }) describe('Unflatten', function () {