Have you ever encountered a "circular reference" error when working with JSON?

In this tutorial, I'll explain what this error means as well as how to fix it.

This error, in my experience, occurs when you try to convert an object with circular references to JSON. You may have experienced this error while doing this or something else.

First, let's understand what a circular reference is in JavaScript.

What is a Circular Reference in JS?

An object in JavaScript can have different data types for properties. Here's an example of an object:

const obj = {
  name: "Dillion",
  isDev: true,
  hobbies: ["singing", "writing"],
  age: 100,
}

This object has properties containing values of the string, boolean, array, and number data type.

In objects, you can also have a nested object. Here's what I mean:

obj.languages = {
  first: "javascript",
  second: "java",
}

console.log(obj)
// {
//   name: "Dillion",
//   isDev: true,
//   hobbies: ["singing", "writing"],
//   age: 100,
//   languages: {
//     first: "javascript",
//     second: "java"
//   }
// }

Here, we add a languages property, which holds an object value containing the first and second properties.

To access the properties of the nested object, you can use the dot notation (or bracket notation) like this:

obj.languages.first
obj.languages.second

I have a video version of this topic you can also check out.

Now, what if we have a nested object that points to the original object? Take a look at this example:

obj.itself = obj

Here, we add an itself property, and assign it an object reference, which is obj. This is a circular reference. I'll show you why it is called a circular reference.

Let's try to print obj to the console:

// {
//   name: "Dillion",
//   isDev: true,
//   hobbies: ["singing", "writing"],
//   age: 100,
//   languages: {
//     first: "javascript",
//     second: "java"
//   },
//   itself: [Circular *1]
// }

In the console, the value of the itself property shows [Circular *1]. This is a notation that the property points back to the object, and trying to log the value of that property will result in an endlessly nested object.

This means that the itself property will look like this:

{
  name: "Dillion",
  isDev: true,
  hobbies: ["singing", "writing"],
  age: 100,
  languages: {
    first: "javascript",
    second: "java"
  },
  itself: {
    name: "Dillion",
    isDev: true,
    hobbies: ["singing", "writing"],
    age: 100,
    languages: {
      first: "javascript",
      second: "java"
    },
    itself: {
      name: "Dillion",
      // ...
    }
  }
}

Here's a graphical illustration of this:

image-54
Graphical illustration of a cyclic reference

As you can see in this object, the itself property has all the properties of the object, including itself, which again has all the properties of the object, including itself and it keeps going on and on without any end.

With this circular reference of the property to the object, we can access the first property like this:

obj.itself.itself.itself.itself.itself.itself.first

This many itselfs still work due to circular reference.

We can also access the name property in obj like this:

obj.itself.itself.itself.itself.name

Since itself has a reference to obj, we can access the name property from any of the nested itselfs.

Now let's see how this circular reference pattern applies to JSON.

JSON's Issue with Circular References

Let's look at our initial object again:

const obj = {
  name: "Dillion",
  isDev: true,
  hobbies: ["singing", "writing"],
  age: 100,
}

When you stringify this object to fit the JSON structure, here's the result:

const stringified = JSON.stringify(obj)

console.log(stringified)
// {
//   "name":"Dillion",
//   "isDev":true,
//   "hobbies":["singing","writing"],"age":100
// }

JSON stringify goes through the properties from the first one being "name" to the last one being "hobbies".

What if we had our nested languages object stringified?

obj.languages = {
  first: "javascript",
  second: "java",
}

const stringified = JSON.stringify(obj)

console.log(stringified)
// {
//   "name":"Dillion",
//   "isDev":true,
//   "hobbies":["singing","writing"],"age":100,
//   "languages":{
//     "first":"javascript",
//     "second":"java"
//   }
// }

As you see here, the stringify method goes through the properties from the first one being "name" to the last one being "languages". When it gets to languages, it goes through the properties of the object which are first and second.

Now let's introduce a circular reference and see what happens:

obj.itself = obj

const stringified = JSON.stringify(obj)

console.log(stringified)
// TypeError: Converting circular structure to JSON

Now we get an error: TypeError: Converting circular structure to JSON.

What's the problem here?

By stringifying an object with a circular reference, that's an infinite stringification process.

Let's say JSON stringify tried to go through this object, here's what happens: the stringify method goes from the first property in obj which is name to the last property itself. When it gets to itself, it stringifies the properties of this object. The value of the itself object is the obj object, so stringify goes through from name to itself again. By coming across itself, it has to go through again.

Here's an illustration explaining what happens:

image-55

As you see here, this results in an infinite loop because stringify doesn't know when to stop. Everytime it comes across itself, it has to go through this object that has itself. It will keep stringifying forever.

This is basically impossible. So when the stringify method comes across a circular reference in an object, it just throws an error immediately. There's no need to waste its time 😅

So if you come across this error when building applications, how do you solve it?

How to Solve the Circular Reference Error

There are a couple of ways you can solve this problem. You can use libraries or implement a solution yourself.

One major way to solve this is by using serialization. This process involves serializing the object to remove some properties from an object before converting it to JSON.

In this process, you can remove properties you are not interested in, or in our case, properties that can cause errors.

Here's a simple solution:

obj.itself = obj

function replacer(key, value) {
  if(key === 'itself') {
    return null
  }

  return value
}

const stringified = JSON.stringify(obj, replacer)

console.log(stringified)
// {
//   "name":"Dillion",
//   "isDev":true,
//   "hobbies":["singing","writing"],"age":100,
//   "languages":{
//     "first":"javascript",
//     "second":"java"
//   },
//   "itself":null
// }

What we've done here is use the replacer argument of JSON.stringify to modify the itself property.

In the replacer function, we check for the key itself, and return the value null for that key. This way, JSON.stringify replaces the circular reference value with null during stringifying, thereby avoiding infinite stringification.

Wrapping Up

If you build applications with JavaScript, you may have come across this cyclic reference error one way or the other.

In this article, I've explained what this error is, and why it exists when converting an object to JSON.

If you enjoyed this article, please share it with others 🙏🏾