Print Circular Structures in JSON Format in JavaScript

Introduction

Dealing with data structures in JavaScript can sometimes be tricky, especially when we encounter things like circular structures. In this article, we'll explore what circular structures are, why they pose challenges when we try to print them in a JSON format, and how we can get this to work for our use-case.

Circular Structures in JavaScript

In JavaScript, a circular structure is an object that references itself, either directly or indirectly. This means that an object's property refers back to the object, forming a loop or "circle" of references. Here's a simple example:

let circularObject = {};
circularObject.myself = circularObject;

In this example, circularObject is a circular structure because the myself property refers back to circularObject itself.

Circular structures can also be more complex, with nested objects that eventually reference the parent object, or even arrays where an element refers back to the array.

let circularObject = {
    child: {
        parent: circularObject
    }
};

let circularArray = [1, 2, 3];
circularArray.push(circularArray);

In the first example, the parent property of the child object refers back to circularObject. In the second example, we add circularArray as an element of itself.

While circular structures may be useful in certain scenarios, they will cause problems when we want to convert them to a string format like JSON. In the next sections, we'll explore why this is the case and how we can print circular structures in a JSON-like format.

Why JSON.stringify() Fails with Circular Structures

Unfortunately, as you'll see, the JSON.stringify() method isn't able to properly handle circular structures.

When JSON.stringify() encounters such a structure, it throws the error TypeError: Converting circular structure to JSON or TypeError: cyclic object value. This is because the method attempts to access the object's properties recursively, but in the case of a circular structure, this would result in an infinite loop.

let circularObject = {};
circularObject.myself = circularObject;

try {
    JSON.stringify(circularObject);
} catch (e) {
    console.error(e.toString());
}

Running this code will result in the following output:

$ node circular.js
TypeError: Converting circular structure to JSON

The error is thrown because JSON.stringify() is basically trying to access circularObject.myself.myself.myself... (and so on).

Printing Circular Structures

So, how can we print circular structures in a JSON-like format? Thankfully, there are a few methods we can use to get around the limitations of JSON.stringify().

One way is to use a custom replacer function with JSON.stringify(). And another way is to use the util package's inspect() function, both of which we'll go into in more detail in the following sections.

Using JSON.stringify() with a Custom Replacer Function

When dealing with circular structures in JavaScript, the JSON.stringify() method can be a bit tricky. As we've discussed, it throws an error when it encounters a circular reference. However, there's a neat workaround for this using a custom replacer function.

The JSON.stringify() method accepts two additional parameters: a replacer and a space. The replacer can be a function that alters the behavior of the stringification process, or an array of String and Number objects.

In our case, we'll use a function that keeps track of the objects we've already serialized, allowing us to handle circular references.

Here's an example:

let cache = [];
let circularObject = {};
circularObject.circularRef = circularObject;

let str = JSON.stringify(circularObject, (key, value) => {
    if (typeof value === 'object' && value !== null) {
        if (cache.includes(value)) {
            // Circular reference found, discard key
            return;
        }
        // Store value in our collection
        cache.push(value);
    }
    return value;
});
cache = null; // Enable garbage collection

console.log(str); // {"circularRef":{}}

In this code, we created a circular object and a cache to keep track of the objects we've already visited. Our replacer function checks if the current value is an object and if it's already in our cache. If it is, we found a circular reference and we discard the key. Otherwise, we add the object to our cache and return the value.

Note: This approach discards the keys associated with circular references, so the resulting JSON won't be a perfect mirror of the original object. However, it does allow us to serialize circular structures without errors.

Using util.inspect()

If you're working with Node.js, there's an even simpler solution to print circular structures: the util.inspect() method. This method returns a string representation of an object, similar to JSON.stringify(), but it handles circular references gracefully.

Here's an example:

const util = require('util');

let circularObject = {};
circularObject.circularRef = circularObject;

console.log(util.inspect(circularObject));

When you run this code, you'll see something like this:

{ circularRef: [Circular] }
Free eBook: Git Essentials

Check out our hands-on, practical guide to learning Git, with best-practices, industry-accepted standards, and included cheat sheet. Stop Googling Git commands and actually learn it!

The [Circular] placeholder indicates a circular reference to the root of the current object.

Note: The util.inspect() method is a Node.js specific feature and it's not available in browser-side JavaScript. It's great for debugging or logging in a Node.js environment, but not suitable for serializing data to be sent over a network.

One of the great things about util.inspect() is that it also accepts an options object that allows you to customize the output. For instance, you can set the depth option to null to inspect all objects, regardless of depth.

console.log(util.inspect(circularObject, { depth: null }));

This will print the entire object, no matter how deep the circular references go. So be careful with large, complex objects - you may be getting more ouptput than you want.

Using Third-Party Libraries

While native JavaScript methods can help us handle circular structures to some extent, there are certain scenarios where they might fall short. This is where we look to third-party libraries for help. These libraries are specifically designed to handle circular structures and can make our task significantly easier. Some popular libraries that we can use are circular-json and flatted.

Example: Using the circular-json Library

Let's take a look at how we can use the circular-json library to print circular structures in a JSON-like format. The circular-json library provides a stringify() function, similar to JSON.stringify(), but with the added ability to handle circular structures.

First, we need to install the library. You can do this by running the following command in your terminal:

$ npm install circular-json

Once installed, we can use it in our code like this:

const CircularJSON = require('circular-json');

let circularObject = {};
circularObject.circularRef = circularObject;
let serialized = CircularJSON.stringify(circularObject);

console.log(serialized);

In the above example, we create a circular structure, where circularObject.circularRef refers back to circularObject itself. When we try to serialize this using CircularJSON.stringify(), it successfully converts the object into a string format without throwing any error. The output will look something like this:

{"circularRef":"~"}

As you can see, the circular-json library replaces the circular reference with a special character (~), indicating a circular structure.

Heads up: The circular-json library is deprecated and not maintained anymore. However, it still works fine for most use-cases and serves as a good example of how third-party libraries can help us handle circular structures in JavaScript. Just be sure to check if it has any open high-severity vulnerabilities before using it in production code.

Example: Using the flatted Library

One of the third-party libraries that can handle circular references in JavaScript is the flatted library. This library offers a simple API similar to JSON, with Flatted.stringify() and Flatted.parse() methods. Let's see how we can use it to print a circular structure.

First, you need to install the flatted library. You can do this using npm:

$ npm install flatted

Once installed, you can use it to stringify and parse circular structures. Here's an example:

const Flatted = require('flatted');

let circularObject = {};
circularObject.self = circularObject;

console.log(Flatted.stringify(circularObject));

This will output a string with a special syntax that represents the circular reference:

{"self":"$"}

You can then parse this string back into an object with the Flatted.parse() method:

let parsedObject = Flatted.parse(Flatted.stringify(circularObject));

console.log(parsedObject === parsedObject.self);

This will output true, showing that the circular reference has been preserved.

Note: The flatted library uses a special syntax to represent circular references, which may not be compatible with other JSON parsers. Make sure to use the Flatted.parse() method to correctly interpret this syntax.

Conclusion

Circular structures can pose a challenge when trying to print or serialize them in JavaScript. The built-in JSON.stringify() method fails with circular structures, but we can overcome this limitation by using a custom replacer function, the util.inspect() method, or third-party libraries like circular-json and flatted. These tools provide different ways to handle circular references, allowing us to print or serialize circular structures in a JSON-like format.

Last Updated: August 30th, 2023
Was this article helpful?

Improve your dev skills!

Get tutorials, guides, and dev jobs in your inbox.

No spam ever. Unsubscribe at any time. Read our Privacy Policy.

Project

React State Management with Redux and Redux-Toolkit

# javascript# React

Coordinating state and keeping components in sync can be tricky. If components rely on the same data but do not communicate with each other when...

David Landup
Uchechukwu Azubuko
Details

Getting Started with AWS in Node.js

Build the foundation you'll need to provision, deploy, and run Node.js applications in the AWS cloud. Learn Lambda, EC2, S3, SQS, and more!

© 2013-2024 Stack Abuse. All rights reserved.

AboutDisclosurePrivacyTerms