概要
JavaScriptでデータを保存・送受信・復元する際に使われるのが シリアライズ(serialize) と デシリアライズ(deserialize) である。
標準的には JSON.stringify()
/ JSON.parse()
が使われるが、これは 万能ではない。
特に以下のような構造は、JSONでは正しく保存・復元できない:
-
Date
オブジェクト → 文字列化される -
Map
,Set
,RegExp
→ 通常のObjectになる - 関数 → 保存されない
- クラスインスタンス → プロトタイプ消失
- 循環参照 →
TypeError
この記事では、JSONの限界を乗り越える設計パターンと代替手段、カスタム復元戦略を体系化する。
JSON.stringify の基本仕様
const obj = {
name: 'Toto',
created: new Date(),
greet: () => 'hi',
};
console.log(JSON.stringify(obj));
出力例:
{"name":"Toto","created":"2025-03-30T12:00:00.000Z"}
- ✅ Date → ISO8601文字列になる
- ❌ 関数 → 無視される
- ❌ プロトタイプ情報 → 消失
限界①:関数・クラスインスタンスが失われる
class User {
constructor(name) {
this.name = name;
}
greet() {
return `Hi, ${this.name}`;
}
}
const u = new User('Toto');
const json = JSON.stringify(u);
const parsed = JSON.parse(json);
parsed.greet(); // ❌ TypeError: parsed.greet is not a function
→ ✅ greet()
メソッドはプロトタイプにあり、JSON化では失われる
限界②:循環参照は保存できない
const a = {};
a.self = a;
JSON.stringify(a); // ❌ TypeError: Converting circular structure to JSON
解決①:カスタム toJSON 実装による制御
class User {
constructor(name) {
this.name = name;
this.created = new Date();
}
toJSON() {
return {
name: this.name,
created: this.created.toISOString(),
__type: 'User'
};
}
}
→ ✅ 特定の型としてシリアライズ可能
解決②:カスタム reviver による復元処理
const str = JSON.stringify(u);
const revived = JSON.parse(str, (key, value) => {
if (value?.__type === 'User') {
const user = new User(value.name);
user.created = new Date(value.created);
return user;
}
return value;
});
→ ✅ JSON.parse
第2引数で「復元処理」を実装可能
→ ✅ 型付きデータの保存→復元を安全に設計できる
解決③:構造保存向きの代替手段
ライブラリ / 手法 | 特徴 |
---|---|
flatted |
循環参照をサポートするJSON互換フォーマット |
superjson |
Date, Map, Set, RegExpなどの復元に対応 |
structuredClone() |
DOM標準・複製のみ(永続化には使えない) |
msgpack / protobuf
|
バイナリフォーマット・構造保持に優れる |
再設計の戦略:何を残し、何を捨てるか
✅ 状態だけ残す:
const snapshot = JSON.stringify(instance); // → DB保存 / API送信用
✅ 振る舞い(メソッド)も復元したい:
// toJSON / reviver または class構造 + factory関数
✅ 構造そのまま複製したい:
// structuredClone() or flatted
よくある落とし穴
❌ Dateをそのまま比較する
JSON.parse(json).created instanceof Date; // ❌ false
→ ✅ new Date(value)
で復元が必要
❌ メソッドが復元されると思っている
→ メソッドは保存されない → prototype再構築が必要
設計判断フロー
① データだけ保存? → JSONで十分
② メソッドや型情報も保存? → toJSON + reviver
③ Map/Set/Date/RegExpも使う? → superjson or flatted
④ 循環参照あり? → flatted or custom traversal
⑤ バイナリで高速化したい? → msgpack / protobuf
結語
シリアライズは、単なる文字列変換ではない。
それは「どの情報を残し、どの構造を捨てるか」という設計的決断のプロセスである。
- JSONは軽くて早いが、構造は失う
- toJSON / reviver は保存と復元を設計に取り込む術
- データの“意味”を維持するためには、保存形式の設計こそが最重要
構造を残すのか、値だけ残すのか――それを選ぶのは、設計者である。