JSON troubleshooting

TypeError: Converting circular structure to JSON

Read the V8 circular path, find the property that closes the loop and serialize plain data instead. Covers Firefox, Safari, Python, Go, .NET and Jackson.

What the error means

JSON can only describe trees, where each object or array sits inside one parent. JSON.stringify met an object that refers back to itself or to an object it is still serializing, so the output would never end. V8 prints the loop as a path: the --> line is where the cycle starts, each | line is one step, and the --- line names the property that points back. Typical sources are parent links in trees, framework objects such as HTTP requests and sockets, DOM nodes, and ORM entities with two-way relations.

Exact messages by runtime

Messages depend on the input and runtime version. Node and Python rows were reproduced locally. Other rows cite official documentation or source. Rows marked as fragments or templates preserve the source wording and are not complete exception transcripts. Browsers and libraries may append location details.

RuntimeMessageInput and evidence
JavaScript / V8TypeError: Converting circular structure to JSONconst a = {}; a.self = a; JSON.stringify(a)
Reproduced in Node.js 24.21.0; V8 adds the path lines shown in the examples
FirefoxTypeError: cyclic object valueThe same input
Official SpiderMonkey message, Firefox 158 source
Safari / JavaScriptCoreTypeError: JSON.stringify cannot serialize cyclic structures.The same input
JavaScriptCore source (WebKit tag webkitgtk-2.54.1)
PythonValueError: Circular reference detecteda = {}; a['self'] = a; json.dumps(a)
Reproduced in Python 3.14.4
Python 3.14+, notewhen serializing dict item 'self'Printed under the ValueError
Reproduced in Python 3.14.4; notes were added in CPython 3.14
Go / encoding/jsonjson: unsupported value: encountered a cycle via *main.Nodejson.Marshal of a *Node whose Next field points back to it
Source-derived from Go 1.27 encode.go; Go starts checking after 1000 nested pointers
C# / System.Text.JsonA possible object cycle was detected. This can either be due to a cycle or if the object depth is larger than the maximum allowed depth of 64. Consider using ReferenceHandler.Preserve on JsonSerializerOptions to support cycles.Two objects that reference each other
Official .NET 10 resource string with the default depth of 64; path omitted
C# / Json.NETSelf referencing loop detected for property '{0}' with type '{1}'.Source template
Json.NET 13.0.4 source template; path suffix omitted
Java / JacksonInfinite recursion (StackOverflowError)Two entities that reference each other
Official Jackson 2.22 source text; the reference chain follows

Causes you can reproduce

A child keeps a reference to its parent

Trees with parent links, linked lists and graphs all contain cycles. The --- line names the back-reference, here the parent property of the child.

JAVASCRIPT
const parent = { name: 'root' };
const child = { name: 'leaf', parent };
parent.child = child;
JSON.stringify(parent);
// TypeError: Converting circular structure to JSON
//     --> starting at object with constructor 'Object'
//     |     property 'child' -> object with constructor 'Object'
//     --- property 'parent' closes the circle

A Node.js HTTP object was serialized

Requests, responses, sockets and HTTP client errors hold references to each other. Passing one to JSON.stringify, to res.json or to a logger that stringifies prints a path through ClientRequest, Socket or HTTPParser. Serialize the fields you need instead, such as the status code and the body.

JAVASCRIPT
import http from 'node:http';
const request = http.get('http://example.com/');
request.on('error', () => {});
JSON.stringify(request);
// TypeError: Converting circular structure to JSON
//     --> starting at object with constructor 'ClientRequest'
//     |     property 'agent' -> object with constructor 'Agent'
//     |     property 'sockets' -> object with constructor 'Object'
//     |     ...
//     |     index 0 -> object with constructor 'Socket'
//     --- property '_httpMessage' closes the circle

Two records reference each other

An ORM or a hand-built model often links a customer to orders and each order back to the customer. Python 3.14 lists the path in notes, from the inner value outward.

PYTHON
import json
customer = {"name": "Ada"}
order = {"id": 7, "customer": customer}
customer["orders"] = [order]
json.dumps(customer)
# ValueError: Circular reference detected
# when serializing dict item 'customer'
# when serializing list item 0
# when serializing dict item 'orders'

How to fix it

  1. Read the path. In V8 the --- line names the property that points back. In Python 3.14 the notes list the path from the inner value outward.
  2. Decide what the receiver needs from that reference. Usually an ID is enough, not the whole parent object.
  3. Serialize plain data built for output: pick the fields, replace back-references with IDs, or give the class a toJSON method that does this.
  4. For logs, use a logger or util.inspect, which marks cycles as [Circular *1] instead of throwing.

Send IDs instead of back-references

An explicit output shape also keeps internal fields out of the JSON.

JAVASCRIPT
const parent = { id: 1, name: 'root', children: [] };
const child = { id: 2, name: 'leaf', parent };
parent.children.push(child);

const text = JSON.stringify({
  id: parent.id,
  name: parent.name,
  children: parent.children.map(c => ({ id: c.id, name: c.name, parentId: c.parent.id })),
});
console.log(text);
// {"id":1,"name":"root","children":[{"id":2,"name":"leaf","parentId":1}]}

Replace only real cycles with a replacer

Use this for diagnostics or when the receiver accepts a placeholder. Unlike a WeakSet of every object seen, it keeps an object that two branches share and replaces only references back to an ancestor. It must be a function expression because it reads this.

JAVASCRIPT
function withoutCycles() {
  const ancestors = [];
  return function (key, value) {
    if (typeof value !== 'object' || value === null) return value;
    // `this` is the object that holds `key`; drop entries that are not its ancestors
    while (ancestors.length > 0 && ancestors.at(-1) !== this) ancestors.pop();
    if (ancestors.includes(value)) return '[Circular]';
    ancestors.push(value);
    return value;
  };
}

const shared = { label: 'shared' };
const node = { a: shared, b: shared };
node.self = node;
console.log(JSON.stringify(node, withoutCycles()));
// {"a":{"label":"shared"},"b":{"label":"shared"},"self":"[Circular]"}

Build a Python payload with IDs

Choose the fields to send and refer to related records by ID.

PYTHON
import json
customer = {"id": 1, "name": "Ada"}
order = {"id": 7, "customer": customer}
customer["orders"] = [order]
payload = {
    "id": customer["id"],
    "name": customer["name"],
    "orders": [{"id": o["id"], "customer_id": o["customer"]["id"]} for o in customer["orders"]],
}
print(json.dumps(payload))
# {"id": 1, "name": "Ada", "orders": [{"id": 7, "customer_id": 1}]}

Frequently asked questions

What does closes the circle mean?

The property on the --- line points back to an object that is already on the path being serialized. Leaving out or replacing that one reference in the output breaks the loop.

Why does the error mention ClientRequest, Socket or HTTPParser?

A Node.js HTTP object reached JSON.stringify, directly or through res.json, a logger or an HTTP client error that holds the request. Serialize only the data you need, such as the status code and the response body.

Is a WeakSet replacer safe?

A replacer that drops every object it has seen before also removes shared objects that are not circular, so data disappears silently. Track only the ancestors of the current value, as the replacer above does.

How do I fix it in .NET or Java?

System.Text.Json: set ReferenceHandler to IgnoreCycles or Preserve. Json.NET: use ReferenceLoopHandling.Ignore. Jackson: use @JsonManagedReference and @JsonBackReference, or @JsonIgnore on the back-reference. A DTO without back-references works everywhere.

Standards and diagnostic sources

RFC 8259 and ECMA-404 define JSON syntax. Diagnostic wording belongs to the parser. The table links each cited message to its source.