flatted vs json-stringify-safe vs circular-json
循環参照を含むJavaScriptオブジェクトの安全なシリアライズ
flattedjson-stringify-safecircular-json類似パッケージ:

循環参照を含むJavaScriptオブジェクトの安全なシリアライズ

circular-jsonflattedjson-stringify-safeは、JavaScriptオブジェクトに循環参照(サーキュラーリファレンス)が含まれている場合でも、それを安全にJSON文字列に変換できるようにするnpmパッケージです。標準のJSON.stringify()は循環参照を含むオブジェクトを処理するとエラーを投げるため、これらのパッケージはエラーログの出力、オブジェクトの永続化、プロセス間通信などの場面で役立ちます。circular-jsonflattedはシリアライズとデシリアライズの両方をサポートし、元のオブジェクト構造を復元可能ですが、json-stringify-safeは主に人間が読むことを目的とした安全な文字列化のみを提供し、デシリアライズ機能はありません。

npmのダウンロードトレンド

3 年

GitHub Starsランキング

統計詳細

パッケージ
ダウンロード数
Stars
サイズ
Issues
公開日時
ライセンス
flatted89,560,5621,12038.4 kB12日前ISC
json-stringify-safe34,666,274554-711年前ISC
circular-json2,206,475610-07年前MIT

サーキュラーリファレンスを含むオブジェクトのシリアライズ:circular-json vs flatted vs json-stringify-safe

JavaScriptで開発していると、オブジェクトが自分自身や他のプロパティを通じて循環参照(サーキュラーリファレンス)を持つケースに遭遇します。標準のJSON.stringify()はこのようなオブジェクトを処理できず、TypeError: Converting circular structure to JSONというエラーを投げます。この問題を解決するために、circular-jsonflattedjson-stringify-safeといったnpmパッケージが登場しました。これらはそれぞれ異なるアプローチで循環参照を安全にシリアライズ・デシリアライズ可能にします。本記事では、実際のコード例を交えながら、各パッケージの技術的特徴と使いどころを詳しく比較します。

⚠️ 重要な前提:circular-jsonは非推奨

まず最初に明確にしておきます — circular-jsonは公式に非推奨(deprecated)されています。npmページおよびGitHubリポジトリには「Do not use. Use flatted instead.」と明記されており、新規プロジェクトでの使用は強く推奨されません。以下では比較のために説明しますが、実際の選定ではflattedまたはjson-stringify-safeを検討すべきです。

🔄 シリアライズ方式の違い:置換 vs 安全な文字列化

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] }

ただし、この方法はカスタムフォーマットに依存しており、他のシステムとの互換性がありません。

flatted

flattedは、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-safe

json-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]" } → 参照は失われる

この方式は、ログ出力やデバッグ用途に最適です。完全なオブジェクト復元は不要で、「何が循環していたか」を人間が読める形で残すことが目的です。

🛠️ API設計と使いやすさ

circular-json(非推奨)

グローバルメソッドとしてCircularJSON.stringify()CircularJSON.parse()を提供します。使い方は直感的ですが、非推奨であるため新しいプロジェクトでは避けるべきです。

flatted

ESM/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]"
//   }
// }

🧪 実用シナリオ別の選定ガイド

シナリオ1: オブジェクトを完全に復元する必要がある(例: セッション保存、IPC通信)

  • 推奨: flatted
  • 理由: 循環参照を保持したままシリアライズ・デシリアライズが可能。circular-jsonの後継であり、安定してメンテナンスされている。

シナリオ2: ログ出力やエラーレポート(人間が読むだけで復元不要)

  • 推奨: json-stringify-safe
  • 理由: [Circular]という明確なマーカーで循環箇所を示し、可読性が高い。軽量で依存が少ない。

シナリオ3: 新規プロジェクトでcircular-jsonを使う

  • 非推奨
  • 理由: 公式に非推奨とされており、バグ修正やセキュリティ更新が保証されない。

📊 技術的特性比較表

特性circular-jsonflattedjson-stringify-safe
循環参照の扱いプレースホルダーで置換プレースホルダーで置換(配列ベース)"[Circular]"文字列に置換
デシリアライズ対応
標準JSONとの互換性❌(独自フォーマット)❌(独自フォーマット)✅(有効なJSON)
人間可読性高(ログ向け)
メンテナンス状況非推奨アクティブアクティブ
主な用途(非推奨)完全なオブジェクト復元ログ・デバッグ出力

💡 最終的なアドバイス

  • 完全なオブジェクトグラフを保存・復元したい場合flattedを使いましょう。これはcircular-jsonの正当な後継であり、信頼性とシンプルさを兼ね備えています。
  • 単に循環参照を含むオブジェクトをログに出したい場合json-stringify-safeが最適です。安全で、読みやすく、軽量です。
  • circular-jsonは絶対に新規プロジェクトで使わないでください。既存コードで使われている場合は、flattedへの移行を検討しましょう。

これらのツールは、それぞれ異なる問題を解決するために設計されています。あなたのユースケースに合ったものを選ぶことで、堅牢でメンテナンスしやすいコードを書くことができます。

選び方: flatted vs json-stringify-safe vs circular-json

  • flatted:

    flattedは循環参照を含むオブジェクトを完全に復元可能な形式でシリアライズ・デシリアライズする必要がある場合に最適です。circular-jsonの後継として開発され、よりシンプルで信頼性の高い実装を提供します。セッション保存やプロセス間通信など、オブジェクトグラフの完全な再現が必要なシナリオで選択してください。

  • json-stringify-safe:

    json-stringify-safeは、循環参照を含むオブジェクトを人間が読める安全なJSON文字列に変換したい場合(例: エラーログ出力、デバッグ情報の記録)に最適です。デシリアライズ機能は提供しませんが、[Circular]という明確なマーカーで循環箇所を示し、標準JSONとして有効な出力を生成します。

  • circular-json:

    circular-jsonは公式に非推奨(deprecated)とされており、新規プロジェクトでの使用は絶対に避けてください。npmおよびGitHubリポジトリには「Do not use. Use flatted instead.」と明記されています。既存コードで使用されている場合は、flattedへの移行を検討すべきです。

flatted のREADME

flatted

Downloads Coverage Status License: ISC WebReflection status

snow flake

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.


ℹ️ JSON only values

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"}]

toJSON and fromJSON

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

Flatted VS JSON

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.

New in V1: Exact same JSON API

  • Added a reviver parameter to .parse(string, reviver) and revive your own objects.
  • Added a replacer and a space parameter to .stringify(object, replacer, space) for feature parity with JSON signature.

Compatibility

All ECMAScript engines compatible with Map, Set, Object.keys, and Array.prototype.reduce will work, even if polyfilled.

How does it work ?

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'