circular-json vs flatted vs json-stringify-deterministic vs json-stringify-safe
Serializing Complex JavaScript Objects: Circular References and Determinism
circular-jsonflattedjson-stringify-deterministicjson-stringify-safeSimilar Packages:

Serializing Complex JavaScript Objects: Circular References and Determinism

Standard JSON.stringify() fails when encountering circular references and does not guarantee consistent key ordering across environments. These four packages offer specialized solutions: circular-json and flatted enable round-trip serialization of circular structures, json-stringify-safe prevents crashes during logging by masking circles, and json-stringify-deterministic ensures consistent key ordering for hashing or signing.

Npm Package Weekly Downloads Trend

3 Years

Github Stars Ranking

Stat Detail

Package
Downloads
Stars
Size
Issues
Publish
License
circular-json0610-07 years agoMIT
flatted01,11031.5 kB1a year agoISC
json-stringify-deterministic04111.6 kB02 years agoMIT
json-stringify-safe0554-711 years agoISC

Serializing Complex JavaScript Objects: Circular References and Determinism

Standard JSON.stringify() is powerful but has two major limitations for advanced engineering: it throws an error on circular references, and it does not guarantee consistent key ordering. The packages circular-json, flatted, json-stringify-deterministic, and json-stringify-safe address these gaps in different ways. Let's compare how they handle real-world serialization challenges.

⚠️ Handling Circular References: Round-Trip vs. Logging

The most common failure point in JavaScript serialization is the circular reference β€” when object A points to object B, which points back to object A.

circular-json was an early solution that allowed serialization and parsing of circular structures.

  • It maps references to special IDs.
  • Status: Deprecated. Do not use.
// circular-json: Deprecated API
import CircularJSON from 'circular-json';

const obj = { name: 'test' };
obj.self = obj;

// Works, but package is unmaintained
const str = CircularJSON.stringify(obj); 
const parsed = CircularJSON.parse(str);

flatted is the modern replacement for circular-json.

  • It uses a similar reference mapping strategy but is actively maintained.
  • Supports full round-trip (stringify and parse).
// flatted: Modern API
import { stringify, parse } from 'flatted';

const obj = { name: 'test' };
obj.self = obj;

// Recommended approach
const str = stringify(obj); 
const parsed = parse(str); // parsed.self === parsed

json-stringify-safe handles circles by masking them, not preserving them.

  • It prevents the crash but replaces the circle with text.
  • You cannot parse the result back to the original structure.
  • Ideal for logging.
// json-stringify-safe: Logging API
import stringify from 'json-stringify-safe';

const obj = { name: 'test' };
obj.self = obj;

// Does not throw, but output is for reading only
const str = stringify(obj); 
// Output: {"name":"test","self":"[Circular]"}

json-stringify-deterministic focuses on key order, not circles.

  • If you pass a circular object, it will likely throw like standard JSON.
  • It is not designed to solve the circular reference problem.
// json-stringify-deterministic: Key Order API
import stringify from 'json-stringify-deterministic';

const obj = { name: 'test' };
obj.self = obj;

// Will throw TypeError: Converting circular structure to JSON
const str = stringify(obj); 

πŸ”’ Ensuring Consistent Key Order: Hashing and Caching

When generating hashes or cache keys, "{a:1, b:2}" must equal "{b:2, a:1}". Standard JSON does not guarantee this.

circular-json does not guarantee key order.

  • Its primary focus is reference preservation.
  • Not suitable for checksums.
// circular-json: No order guarantee
import CircularJSON from 'circular-json';

const obj = { b: 2, a: 1 };
const str = CircularJSON.stringify(obj); 
// Key order depends on insertion, not sorted

flatted does not guarantee key order.

  • It prioritizes reference integrity over key sorting.
  • Not suitable for checksums.
// flatted: No order guarantee
import { stringify } from 'flatted';

const obj = { b: 2, a: 1 };
const str = stringify(obj); 
// Key order depends on insertion, not sorted

json-stringify-deterministic sorts keys alphabetically.

  • Ensures the same input always yields the same string.
  • Essential for cryptographic signatures or cache keys.
// json-stringify-deterministic: Sorted Keys
import stringify from 'json-stringify-deterministic';

const obj = { b: 2, a: 1 };
const str = stringify(obj); 
// Output: {"a":1,"b":2} (Always sorted)

json-stringify-safe does not guarantee key order.

  • It wraps standard stringify logic primarily for safety.
  • Not suitable for checksums.
// json-stringify-safe: No order guarantee
import stringify from 'json-stringify-safe';

const obj = { b: 2, a: 1 };
const str = stringify(obj); 
// Key order depends on insertion, not sorted

πŸ› οΈ API Simplicity and Integration

How these packages fit into your existing codebase varies by design.

circular-json uses a default export that mimics JSON.

  • Drop-in replacement style, but legacy.
// circular-json: Legacy namespace
import CircularJSON from 'circular-json';
CircularJSON.stringify(data);

flatted uses named exports for clarity.

  • Explicit stringify and parse functions.
// flatted: Named exports
import { stringify, parse } from 'flatted';
stringify(data);

json-stringify-deterministic exports a single function.

  • Often named stringify.
// json-stringify-deterministic: Single function
import stringify from 'json-stringify-deterministic';
stringify(data);

json-stringify-safe exports a single function with optional arguments.

  • Matches JSON.stringify signature (obj, replacer, space).
// json-stringify-safe: Standard signature
import stringify from 'json-stringify-safe';
stringify(data, null, 2);

🌐 Real-World Scenarios

Scenario 1: Saving Application State to LocalStorage

You have a complex state tree with circular references (e.g., parent-child links).

  • βœ… Best choice: flatted
  • Why? You need to restore the state exactly as it was, including links.
// flatted: State persistence
import { stringify, parse } from 'flatted';
localStorage.setItem('state', stringify(appState));
const restored = parse(localStorage.getItem('state'));

Scenario 2: Logging Errors in a Backend Service

You are logging request objects that might contain circular references (e.g., req.res.req).

  • βœ… Best choice: json-stringify-safe
  • Why? You just need to read the log. Crashing the logger is worse than seeing [Circular].
// json-stringify-safe: Error logging
import stringify from 'json-stringify-safe';
logger.error(stringify(errorObject));

Scenario 3: Generating an API Signature

You need to hash a payload to verify integrity. Key order must be consistent.

  • βœ… Best choice: json-stringify-deterministic
  • Why? hash({a:1}) must equal hash({a:1}) regardless of how the object was created.
// json-stringify-deterministic: Signing
import stringify from 'json-stringify-deterministic';
const signature = crypto.createHash('sha256').update(stringify(payload)).digest('hex');

Scenario 4: Legacy Code Maintenance

You encounter circular-json in an old codebase.

  • βœ… Best choice: Refactor to flatted
  • Why? circular-json is deprecated and may have unpatched vulnerabilities.
// Refactor target
// Old: import CircularJSON from 'circular-json';
// New: import { stringify } from 'flatted';

πŸ“Š Summary: Key Differences

Featurecircular-jsonflattedjson-stringify-safejson-stringify-deterministic
Circular Refsβœ… Supportedβœ… Supported⚠️ Masked ([Circular])❌ Throws Error
Parseableβœ… Yesβœ… Yes❌ Noβœ… Yes
Key Order❌ Unsorted❌ Unsorted❌ Unsortedβœ… Sorted
Statusβ›” Deprecatedβœ… Activeβœ… Stableβœ… Stable
Best ForLegacy SupportState SyncLoggingHashing/Signing

πŸ’‘ The Big Picture

flatted is the go-to solution for data that needs to travel and return with its structure intact. It solves the circular reference problem without sacrificing the ability to parse the data later.

json-stringify-safe is a safety net for observability. It ensures your logging infrastructure never crashes due to unexpected object shapes, trading data fidelity for stability.

json-stringify-deterministic is a tool for integrity. It removes ambiguity from serialization, making it essential for security and caching layers.

circular-json belongs in the past. It served a purpose but has been superseded by better, maintained alternatives.

Final Thought: Don't reach for a circular serializer when you just need to log an error, and don't use a safe logger when you need to generate a hash. Match the tool to the data flow requirement.

How to Choose: circular-json vs flatted vs json-stringify-deterministic vs json-stringify-safe

  • circular-json:

    Do not use this package in new projects. It is officially deprecated and unmaintained. The author explicitly recommends switching to flatted, which offers better performance and compatibility. Using this introduces technical debt and potential security risks.

  • flatted:

    Choose flatted when you need to save and reload complex state objects that contain circular references. It is the modern standard for round-trip serialization, allowing you to stringify and parse data back into its original structure without losing references.

  • json-stringify-deterministic:

    Choose json-stringify-deterministic when you need to generate checksums, signatures, or cache keys from objects. It ensures that two objects with the same data but different key insertion orders produce the exact same JSON string.

  • json-stringify-safe:

    Choose json-stringify-safe for logging and error reporting where you want to avoid crashes if an object happens to be circular. It replaces circular references with a string marker like [Circular] instead of throwing an error, but the result cannot be parsed back.

README for circular-json

CircularJSON

donate Downloads Build Status Coverage Status

Serializes and deserializes otherwise valid JSON objects containing circular references into and from a specialized JSON format.


The future of this module is called flatted

Smaller, faster, and able to produce on average a reduced output too, flatted is the new, bloatless, ESM and CJS compatible, circular JSON parser.

It has now reached V1 and it implements the exact same JSON API.

Please note CircularJSON is in maintenance only and flatted is its successor.


A Working Solution To A Common Problem

A usage example:

var object = {};
object.arr = [
  object, object
];
object.arr.push(object.arr);
object.obj = object;

var serialized = CircularJSON.stringify(object);
// '{"arr":["~","~","~arr"],"obj":"~"}'
// NOTE: CircularJSON DOES NOT parse JS
// it handles receiver and reviver callbacks

var unserialized = CircularJSON.parse(serialized);
// { arr: [ [Circular], [Circular] ],
// obj: [Circular] }

unserialized.obj === unserialized;
unserialized.arr[0] === unserialized;
unserialized.arr.pop() === unserialized.arr;

A quick summary:

  • new in version 0.5, you can specify a JSON parser different from JSON itself. CircularJSON.parser = ABetterJSON; is all you need.
  • uses ~ as a special prefix symbol to denote which parent the reference belongs to (i.e. ~root~child1~child2)
  • reasonably fast in both serialization and deserialization
  • compact serialization for easier and slimmer transportation across environments
  • tested and covered over nasty structures too
  • compatible with all JavaScript engines

Node Installation & Usage

npm install --save circular-json
'use strict';

var
  CircularJSON = require('circular-json'),
  obj = { foo: 'bar' },
  str
;
  
obj.self = obj;
str = CircularJSON.stringify(obj);

There are no dependencies.

Browser Installation & Usage

  • Global: <build/circular-json.js>
  • AMD: <build/circular-json.amd.js>
  • CommonJS: <build/circular-json.node.js>

(generated via gitstrap)

<script src="build/circular-json.js"></script>
'use strict';

var CircularJSON = window.CircularJSON
  , obj = { foo: 'bar' }
  , str
  ;
  
obj.self = obj;
str = CircularJSON.stringify(obj);

NOTE: Platforms without native JSON (i.e. MSIE <= 8) requires json3.js or similar.

It is also a bad idea to CircularJSON.parse(JSON.stringify(object)) because of those manipulation used in CircularJSON.stringify() able to make parsing safe and secure.

As summary: CircularJSON.parse(CircularJSON.stringify(object)) is the way to go, same is for JSON.parse(JSON.stringify(object)).

API

It's the same as native JSON, except the fourth parameter placeholder, which circular references to be replaced with "[Circular]" (i.e. for logging).

  • CircularJSON.stringify(object, replacer, spacer, placeholder)
  • CircularJSON.parse(string, reviver)

Bear in mind JSON.parse(CircularJSON.stringify(object)) will work but not produce the expected output.

Similar Libraries

Why Not the @izs One

The module json-stringify-safe seems to be for console.log() but it's completely pointless for JSON.parse(), being latter one unable to retrieve back the initial structure. Here an example:

// a logged object with circular references
{
  "circularRef": "[Circular]",
  "list": [
    "[Circular]",
    "[Circular]"
  ]
}
// what do we do with above output ?

Just type this in your node console: var o = {}; o.a = o; console.log(o);. The output will be { a: [Circular] } ... good, but that ain't really solving the problem.

However, if that's all you need, the function used to create that kind of output is probably faster than CircularJSON and surely fits in less lines of code.

Why Not {{put random name}} Solution

So here the thing: circular references can be wrong but, if there is a need for them, any attempt to ignore them or remove them can be considered just a failure.

Not because the method is bad or it's not working, simply because the circular info, the one we needed and used in the first place, is lost!

In this case, CircularJSON does even more than just solve circular and recursions: it maps all same objects so that less memory is used as well on deserialization as less bandwidth too! It's able to redefine those references back later on so the way we store is the way we retrieve and in a reasonably performant way, also trusting the snappy and native JSON methods to iterate.