circular-json、flatted、json-stringify-safeは、JavaScriptオブジェクトに循環参照(サーキュラーリファレンス)が含まれている場合でも、それを安全にJSON文字列に変換できるようにするnpmパッケージです。標準のJSON.stringify()は循環参照を含むオブジェクトを処理するとエラーを投げるため、これらのパッケージはエラーログの出力、オブジェクトの永続化、プロセス間通信などの場面で役立ちます。circular-jsonとflattedはシリアライズとデシリアライズの両方をサポートし、元のオブジェクト構造を復元可能ですが、json-stringify-safeは主に人間が読むことを目的とした安全な文字列化のみを提供し、デシリアライズ機能はありません。
circular-json vs flatted vs json-stringify-safeJavaScriptで開発していると、オブジェクトが自分自身や他のプロパティを通じて循環参照(サーキュラーリファレンス)を持つケースに遭遇します。標準のJSON.stringify()はこのようなオブジェクトを処理できず、TypeError: Converting circular structure to JSONというエラーを投げます。この問題を解決するために、circular-json、flatted、json-stringify-safeといったnpmパッケージが登場しました。これらはそれぞれ異なるアプローチで循環参照を安全にシリアライズ・デシリアライズ可能にします。本記事では、実際のコード例を交えながら、各パッケージの技術的特徴と使いどころを詳しく比較します。
circular-jsonは非推奨まず最初に明確にしておきます — circular-jsonは公式に非推奨(deprecated)されています。npmページおよびGitHubリポジトリには「Do not use. Use flatted instead.」と明記されており、新規プロジェクトでの使用は強く推奨されません。以下では比較のために説明しますが、実際の選定ではflattedまたはjson-stringify-safeを検討すべきです。
circular-json(非推奨)circular-jsonは、循環参照を特殊なプレースホルダー(例: "$ref$0")に置き換えることで、JSONとして有効な文字列を生成します。デシリアライズ時は、そのプレースホルダーを元の参照に戻します。
// circular-json(非推奨)
const CircularJSON = require('circular-json');
const obj = { a: 1 };
obj.self = obj;
const serialized = CircularJSON.stringify(obj);
// '{"a":1,"self":"$ref$0"}'
const deserialized = CircularJSON.parse(serialized);
// { a: 1, self: [Circular] }
ただし、この方法はカスタムフォーマットに依存しており、他のシステムとの互換性がありません。
flattedflattedは、circular-jsonの後継として開発され、同じ作者によってメンテナンスされています。内部的には同様の置換方式を採用していますが、よりシンプルで信頼性の高い実装になっています。また、JSON.stringify/JSON.parseの直接的な代替として設計されており、APIも自然です。
// flatted
const { stringify, parse } = require('flatted');
const obj = { a: 1 };
obj.self = obj;
const serialized = stringify(obj);
// '[{"a":1,"self":"$"}]'
const deserialized = parse(serialized);
// { a: 1, self: [Circular] }
flattedは、内部で配列ベースの表現を使用し、参照をインデックスで管理します。これにより、よりコンパクトで一貫性のある出力が得られます。
json-stringify-safejson-stringify-safeは、循環参照を「削除」または「置換」するのではなく、安全に文字列化できる形に変換します。具体的には、循環参照に到達した時点で、その値を"[Circular]"という文字列に置き換えます。デシリアライズ機能は提供していません。
// json-stringify-safe
const stringify = require('json-stringify-safe');
const obj = { a: 1 };
obj.self = obj;
const serialized = stringify(obj);
// '{"a":1,"self":"[Circular]"}'
// デシリアライズはできません
const deserialized = JSON.parse(serialized);
// { a: 1, self: "[Circular]" } → 参照は失われる
この方式は、ログ出力やデバッグ用途に最適です。完全なオブジェクト復元は不要で、「何が循環していたか」を人間が読める形で残すことが目的です。
circular-json(非推奨)グローバルメソッドとしてCircularJSON.stringify()とCircularJSON.parse()を提供します。使い方は直感的ですが、非推奨であるため新しいプロジェクトでは避けるべきです。
flattedESM/CJS両対応で、import { stringify, parse } from 'flatted'またはconst { stringify, parse } = require('flatted')で利用可能です。JSONオブジェクトとほぼ同じインターフェースを持ち、既存コードへの置き換えが容易です。
// flattedはJSONと同じ感覚で使える
const originalStringify = JSON.stringify;
JSON.stringify = stringify; // 一時的に差し替え可能(注意は必要)
json-stringify-safe関数としてエクスポートされるため、const stringify = require('json-stringify-safe')で使用します。JSON.stringifyの第2引数(replacer)や第3引数(space)もサポートしており、柔軟な整形が可能です。
const safeStringify = require('json-stringify-safe');
const obj = { a: { b: 1 } };
obj.a.c = obj.a;
console.log(safeStringify(obj, null, 2));
// {
// "a": {
// "b": 1,
// "c": "[Circular]"
// }
// }
flattedcircular-jsonの後継であり、安定してメンテナンスされている。json-stringify-safe[Circular]という明確なマーカーで循環箇所を示し、可読性が高い。軽量で依存が少ない。circular-jsonを使う| 特性 | circular-json | flatted | json-stringify-safe |
|---|---|---|---|
| 循環参照の扱い | プレースホルダーで置換 | プレースホルダーで置換(配列ベース) | "[Circular]"文字列に置換 |
| デシリアライズ対応 | ✅ | ✅ | ❌ |
| 標準JSONとの互換性 | ❌(独自フォーマット) | ❌(独自フォーマット) | ✅(有効なJSON) |
| 人間可読性 | 中 | 中 | 高(ログ向け) |
| メンテナンス状況 | 非推奨 | アクティブ | アクティブ |
| 主な用途 | (非推奨) | 完全なオブジェクト復元 | ログ・デバッグ出力 |
flattedを使いましょう。これはcircular-jsonの正当な後継であり、信頼性とシンプルさを兼ね備えています。json-stringify-safeが最適です。安全で、読みやすく、軽量です。circular-jsonは絶対に新規プロジェクトで使わないでください。既存コードで使われている場合は、flattedへの移行を検討しましょう。これらのツールは、それぞれ異なる問題を解決するために設計されています。あなたのユースケースに合ったものを選ぶことで、堅牢でメンテナンスしやすいコードを書くことができます。
flattedは循環参照を含むオブジェクトを完全に復元可能な形式でシリアライズ・デシリアライズする必要がある場合に最適です。circular-jsonの後継として開発され、よりシンプルで信頼性の高い実装を提供します。セッション保存やプロセス間通信など、オブジェクトグラフの完全な再現が必要なシナリオで選択してください。
json-stringify-safeは、循環参照を含むオブジェクトを人間が読める安全なJSON文字列に変換したい場合(例: エラーログ出力、デバッグ情報の記録)に最適です。デシリアライズ機能は提供しませんが、[Circular]という明確なマーカーで循環箇所を示し、標準JSONとして有効な出力を生成します。
circular-jsonは公式に非推奨(deprecated)とされており、新規プロジェクトでの使用は絶対に避けてください。npmおよびGitHubリポジトリには「Do not use. Use flatted instead.」と明記されています。既存コードで使用されている場合は、flattedへの移行を検討すべきです。

Social Media Photo by Matt Seymour on Unsplash
A super light (0.5K) and fast circular JSON parser, directly from the creator of CircularJSON.
Available also for PHP.
Available also for Python.
Available also for Go.
If you need anything more complex than values JSON understands, there is a standard approach to recursion and more data-types than what JSON allows, and it's part of the Structured Clone polyfill.
npm i flatted
Usable via CDN or as regular module.
// ESM
import {parse, stringify, toJSON, fromJSON} from 'flatted';
// CJS
const {parse, stringify, toJSON, fromJSON} = require('flatted');
const a = [{}];
a[0].a = a;
a.push(a);
stringify(a); // [["1","0"],{"a":"0"}]
If you'd like to implicitly survive JSON serialization, these two helpers helps:
import {toJSON, fromJSON} from 'flatted';
class RecursiveMap extends Map {
static fromJSON(any) {
return new this(fromJSON(any));
}
toJSON() {
return toJSON([...this.entries()]);
}
}
const recursive = new RecursiveMap;
const same = {};
same.same = same;
recursive.set('same', same);
const asString = JSON.stringify(recursive);
const asMap = RecursiveMap.fromJSON(JSON.parse(asString));
asMap.get('same') === asMap.get('same').same;
// true
As it is for every other specialized format capable of serializing and deserializing circular data, you should never JSON.parse(Flatted.stringify(data)), and you should never Flatted.parse(JSON.stringify(data)).
The only way this could work is to Flatted.parse(Flatted.stringify(data)), as it is also for CircularJSON or any other, otherwise there's no granted data integrity.
Also please note this project serializes and deserializes only data compatible with JSON, so that sockets, or anything else with internal classes different from those allowed by JSON standard, won't be serialized and unserialized as expected.
.parse(string, reviver) and revive your own objects.space parameter to .stringify(object, replacer, space) for feature parity with JSON signature.All ECMAScript engines compatible with Map, Set, Object.keys, and Array.prototype.reduce will work, even if polyfilled.
While stringifying, all Objects, including Arrays, and strings, are flattened out and replaced as unique index. *
Once parsed, all indexes will be replaced through the flattened collection.
* represented as string to avoid conflicts with numbers
// logic example
var a = [{one: 1}, {two: '2'}];
a[0].a = a;
// a is the main object, will be at index '0'
// {one: 1} is the second object, index '1'
// {two: '2'} the third, in '2', and it has a string
// which will be found at index '3'
Flatted.stringify(a);
// [["1","2"],{"one":1,"a":"0"},{"two":"3"},"2"]
// a[one,two] {one: 1, a} {two: '2'} '2'