---
type: errors
language: javascript
slug: converting-circular-structure
title: "TypeError: Converting circular structure to JSON"
title_tag: "Converting circular structure to JSON の原因と直し方"
summary: >
  自分自身を辿れるオブジェクトを JSON.stringify() に渡すと出ます。親子の相互参照、
  DOM 要素、通信のオブジェクト。どこで輪になっているかの見つけ方と、3つの直し方を載せています。
description: >
  親と子がお互いを持っていると JSON.stringify() は終われません。輪になっている場所の
  見つけ方、置換関数で切る方法、structuredClone との違いまで実行して確かめられます。
status: published
difficulty: 2
minutes: 8

versions:
  verified: "Node 22.22.3"
  since: "ES5"
  deprecated: null
  removed: null

sources:
  - title: "JSON.stringify — ECMAScript® 2026 Language Specification"
    url: "https://tc39.es/ecma262/#sec-json.stringify"
  - title: "SerializeJSONProperty — ECMAScript® 2026 Language Specification"
    url: "https://tc39.es/ecma262/#sec-serializejsonproperty"
  - title: "JSON.stringify() — MDN"
    url: "https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify"

terms: [循環参照, 直列化, 置換関数]

links:
  related:
    - javascript/reference/json/parse
    - javascript/how-to/object/merge
    - javascript/reference/error/error
    - javascript/learn/object/nested

content_updated_at: 2026-09-08
published_at: 2026-09-08
---

**自分自身に辿り着けるオブジェクト**を文字列にしようとしたときに出ます。

```js bad
const node = { name: 'a' };

node.self = node;

JSON.stringify(node);
```
```output
TypeError: Converting circular structure to JSON
    --> starting at object with constructor 'Object'
    --- property 'self' closes the circle
```

`self` を辿るとまた `node` に戻るので、**終われません。**

[dim:2行目から後ろは、どこで輪になったかを教えてくれる補足です。処理系によって出方が違います。]

## 実務でいちばん多い形：親と子がお互いを持つ

```js bad
const parent = { name: '親' };
const child = { name: '子', parent };

parent.child = child;

JSON.stringify(parent);
```
```output
TypeError: Converting circular structure to JSON
    --> starting at object with constructor 'Object'
    |     property 'child' -> object with constructor 'Object'
    --- property 'parent' closes the circle
```

**`parent` → `child` → `parent` で輪になっています。**
補足の行を上から読むと、どの鍵で戻ったかが分かります。

## 同じものが2回出るだけなら平気

```js run
const shared = { n: 1 };

console.log(JSON.stringify({ a: shared, b: shared }));
```
```output
{"a":{"n":1},"b":{"n":1}}
```

**同じものを2か所から指しているだけなら落ちません。**
中身が2回書き出されるだけです。

問題になるのは[key:自分に戻ってこられるとき]だけです。

## 直し方1: 要らない側を落とす

**いちばん簡単で、たいてい正しい方法です。**

```js run
const parent = { name: '親' };
const child = { name: '子', parent };

parent.child = child;

console.log(JSON.stringify(child, (key, value) => (key === 'parent' ? undefined : value)));
```
```output
{"name":"子"}
```

第2引数の関数（[type:置換関数]）が、すべての鍵について呼ばれます。
`undefined` を返した鍵は**出力から消えます。**

送りたい形が決まっているなら、**最初から作り直すほうが確実**です。

```js run
const parent = { name: '親' };
const child = { name: '子', parent };

parent.child = child;

console.log(JSON.stringify({ name: child.name, parentName: child.parent.name }));
```
```output
{"name":"子","parentName":"親"}
```

[em:「全部送って、受け取る側で選ぶ」をやめると、この問題は起きません。]

## 直し方2: 通ったものを覚えて切る

構造が分からないものを、とりあえずログに出したいときに使います。

```js run
const node = { name: 'a' };

node.self = node;

const seen = new WeakSet();

console.log(
  JSON.stringify(node, (key, value) => {
    if (typeof value === 'object' && value !== null) {
      if (seen.has(value)) return '[循環]';
      seen.add(value);
    }
    return value;
  }),
);
```
```output
{"name":"a","self":"[循環]"}
```

**落ちなくなりますが、情報は欠けます。**
記録や調査のためだけに使ってください。

[dim:`WeakSet` を使うと、覚えたオブジェクトが後片付けの邪魔になりません。同じ理由で `Set` より向いています。]

## 直し方3: 複製したいだけなら `structuredClone()`

**JSON を経由するのが目的でないなら、そもそも `JSON.stringify()` は要りません。**

```js run
const node = { name: 'a' };

node.self = node;

const copied = structuredClone(node);

console.log(copied.name);
console.log(copied.self === copied);
console.log(copied === node);
```
```output
a
true
false
```

`structuredClone()` は[key:循環をそのまま保って]複製します。
別のオブジェクトになり、輪の形も残ります。

→ [オブジェクトを結合する](/ja/javascript/how-to/object/merge/)

## 直し方4: `toJSON()` を持たせる

**そのオブジェクトを送るたびに同じ形にしたいなら、こちらです。**

```js run
class TreeNode {
  constructor(name) {
    this.name = name;
    this.children = [];
  }

  add(child) {
    this.children.push(child);
    child.parent = this;
    return this;
  }

  toJSON() {
    return { name: this.name, children: this.children };
  }
}

const root = new TreeNode('根');

root.add(new TreeNode('子'));

console.log(JSON.stringify(root));
```
```output
{"name":"根","children":[{"name":"子","children":[]}]}
```

`JSON.stringify()` は、`toJSON()` があれば**その戻り値を使います。**
`parent` は返していないので、輪になりません。

呼ぶ側は何も書かなくてよいので、[key:忘れようがない]のが利点です。
→ [class — 作る・継承する・隠す](/ja/javascript/reference/class/class/)

## そもそも輪にしない

**保存や送信をするデータなら、親を持たせずに `id` で指すほうが素直です。**

```js run
const rows = [
  { id: 1, name: '根', parentId: null },
  { id: 2, name: '子', parentId: 1 },
];

console.log(JSON.stringify(rows));
```
```output
[{"id":1,"name":"根","parentId":null},{"id":2,"name":"子","parentId":1}]
```

表に入る形と同じです。**受け取った側で木に組み直します。**
循環は「画面で使うための形」であって、[em:送るための形ではありません。]

## 配列でも起きる

```js bad
const list = [1];

list.push(list);

JSON.stringify(list);
```
```output
TypeError: Converting circular structure to JSON
    --> starting at object with constructor 'Array'
    --- index 1 closes the circle
```

配列のときは、鍵の名前ではなく[key:添字]が出ます。

## ついでに：落ちないのに消えるもの

```js bad
console.log(JSON.stringify({ m: new Map([['a', 1]]), s: new Set([1]) }));
```
```output
{"m":{},"s":{}}
```

`Map` と `Set` は、**落ちませんが中身が消えます。**
配列にしてから渡してください。

```js run
console.log(JSON.stringify({ m: [...new Map([['a', 1]])], s: [...new Set([1])] }));
```
```output
{"m":[["a",1]],"s":[1]}
```

→ [Map と Set](/ja/javascript/reference/collection/map-set/)

## どこで起きやすいか

| 出どころ | 何が輪になるか |
|---|---|
| 親子の木構造 | 子が親を持っている |
| DOM の要素 | 要素が親要素・文書・窓を辿れる |
| 通信ライブラリの応答 | 応答が要求を持ち、要求が応答を持つ |
| エラーに付けた文脈 | 例外に付けたオブジェクトが例外を指す |

**ログを送ろうとして落ちる**のが、いちばんよくある出会い方です。

エラーをそのまま `JSON.stringify()` に渡すのも、別の理由で失敗します。

```js bad
console.log(JSON.stringify(new Error('壊れた')));
```
```output
{}
```

→ [Error と try / catch / finally / throw](/ja/javascript/reference/error/error/)

## 探しかたの順序

1. **文言の2行目から後ろを読む。** どの鍵で戻ったかが書いてある
2. その鍵が本当に要るか考える。**たいてい要らない**
3. 要らないなら置換関数で落とすか、送る形を作り直す
4. 中身が分からないものをとりあえず出したいなら、通ったものを覚えて切る
5. 複製が目的なら `structuredClone()` に替える

[dim:このページの実行結果は Node 22.22.3 のものです。文言は処理系と版によって変わります。補足の行が出ない処理系もあります。]

## まとめ

- **自分に戻ってこられる形**だけが問題。同じものを2か所から指すのは平気
- 文言の2行目から後ろに、**どの鍵で輪になったか**が出る
- 直し方は[key:要らない鍵を落とす]・通ったものを覚えて切る・`toJSON()` を持たせる・`structuredClone()`
- **`Map` と `Set` は落ちないが中身が消える**
- **送る形を最初から作るのがいちばん確実**
- `JSON.stringify(error)` は落ちないが `{}` になる
