DEV Community

loading...

I wrote an NPM package to prevent infinite recursion in objects

ricardo profile image Ricardo Luz ・3 min read

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;
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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 = []) {

}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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 {}
}
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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
  }, {})
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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!

And that’s all folks!

Discussion (0)

pic
Editor guide