Sometimes we need to output the data from some objects, but depending on the object structure this simple task could be hard to do, let’s create two small objects to demonstrate this:
const a = { hello: 'world' };
const b = { hey: 'hey', hello: a };
a.newProp = b;
In the example above we have two objects, a and b, and in the end we connect these objects using the newProp attribute, simple right?
But what’s happen when we try to output the a object content, let’s say, to a JSON string?
JSON.stringfy(a)
Uncaught TypeError: Converting circular structure
to JSON at JSON.stringify (<anonymous>) at <anonymous>:1:6
The problem is we created a connection between a and b, but b was connected before with a from the hello attribute, which makes impossible to generate a JSON structure once this connection is infinite.
To solve that we need to create a function that generates a new object without endless recursion, but we also need to keep finite recursion like this:
const enzo = {
name: 'Enzo',
otherSon: null,
}
const validStructure = {
name: 'John',
son: enzo,
otherSon: {
name: 'Ridlav',
},
}
enzo.otherSon = validStructure.otherSon
JSON.stringfy(validStructure)
In this example we have the following structure:
John has 2 sons, one Enzo and other Ridlav.
Enzo has one son that is Ridlav.
The visual representation of this structure will be:
This (strange) structure is a good example of a valid recursion that our function should keep in the final object, to solve that we need to use recursion to remove recursion!
Let start declaring our function and its parameters:
function preventObjectRecursion(root, list = []) {
}
Let understand the parameters of this function:
Root: Will receive the object that we need to change, but will also receive the attributes of the original object in a second call onwards.
List: Will receive the objects in the path from the root to the current attribute, this will help us to check if this attribute was added before
The idea is to call this function to each object attribute checking if this attribute was called before in this same path, but to do that first we need to ignore root with other data types (null, function, boolean, strings and numbers), once those types don’t generate recursion:
if(!root || typeof root !== 'object') return root
After that we need to compare the current root with the objects in the list array, the idea here is to avoid this:
In the example above we have a reference to a in the root and another reference to a inside b attribute, to solve that the list parameter will store all the elements in the path (in this case a, b) and check if this element is one of that, if is we avoid this element to being added in our new array:
if(list.length > 0){
const hasDuplicated = list.some((item: object) => item === root);
if(hasDuplicated) return {}
}
Cool, after this check we need to add the current root in the list path, this will allow analysing this element in the next time that we call our function:
list.push(root)
Finally, we need to analyse the attributes of the current root element and call this function again to remove any recursion, we’ll the Object.keys to do that:
return Object.keys(root)
.reduce((rootClean, key) => {
rootClean[key] = preventObjectRecursion(root[key], [...list])
return rootClean
}, {})
If you want to know more about the reduce method I wrote this article:
#2 Functional approach: Reduce … medium.com
The only difference from the package that I published and the example above is that I used typescript to create the package instead only Javascript, you could check out the result in this Github repo:
rxluz/prevent-object-recursion… github.com
Or simply install from NPM:
$ npm i @rxluz/prevent-object-recursion --save
Also, please feel free to send to me feedbacks from the comments or opening an issue in Github, this repo needs to increase the number of tests so contributors are welcome!
Top comments (0)