Question Details

No question body available.

Tags

c# asp.net-core

Answers (1)

Accepted Answer Available
Accepted Answer
December 9, 2025 Score: 1 Rep: 121,706 Quality: High Completeness: 100%

Can System.Text.Json.Serialization detect long reference cycles?

Yes, System.Text.Json's ReferenceHandler.IgnoreCycles can detect and ignore arbitrarily deep reference cycles.

However JsonSerializerOptions.MaxDepth still applies so if the depth of a back-reference exceeds the maximum serialization nesting depth a JsonException with the message A possible object cycle was detected. will still be thrown. If you expect reference cycles to exceed the default MaxDepth (currently 64) you will need to increase it to account for your expected maximum serialization depth.

That being said, do not set MaxDepth to some very large value such as int.MaxValue. If you really were to try to serialize or deserialize a model with such an extreme nesting depth, System.Text.Json could end up throwing a StackOverflowException which cannot be caught with a try / catch block and will instead terminate your application -- which means your application would be vulnerable to denial-of-service bugs when passed an extremely deep model by an attacker. If you really do expect to have such deep nesting levels you may need to rethink your serialization format.

It looks like the issue may be with cycles that repeat classes, but not objects. These are all many-to-many relations and their mapping tables.
The question now becomes: is there a setting in the JSON serializer to ignore any instance of a class that has already been serialized?

Rather than ignoring instances of a class that have already been serialized, you may preserve references by setting JsonSerializerOptions.ReferenceHandler to ReferenceHandler.Preserve. As explained in How to preserve references and handle or ignore circular references in System.Text.Json:

To preserve references and handle circular references, set ReferenceHandler to Preserve. This setting causes the following behavior:

  • On serialize:

    When writing complex types, the serializer also writes metadata properties ($id, $values, and $ref).

  • On deserialize:

    Metadata is expected (although not mandatory), and the deserializer tries to understand it.

Do note that this option changes your JSON format. When an object is first encountered during serialization, an "$id": "", property is included. Then if the object is again encountered during serialization, it isn't serialized; instead a single "$ref": "" property is serialized in its place.

For instance, if you have some very simple recursive model like this:

class Model { public Model? NestedModel { get; set; } }

And serialize an instance that has a back-reference at depth 3 with options:

JsonSerializerOptions options = new()
{
    ReferenceHandler = ReferenceHandler.Preserve,
    WriteIndented = true
};

The resulting JSON will look like:

{
  "$id": "1",
  "NestedModel": {
    "$id": "2",
    "NestedModel": {
      "$id": "3",
      "NestedModel": {
        "$ref": "1" // The back reference to the root model.
      }
    }
  }
}

Demo fiddle here.