System.Text.Json.JsonException: A possible object cycle was detected which is not supported

When you use System.Text.Json.JsonSerializer to serialize an object that has a cycle, you’ll get the following exception:

System.Text.Json.JsonException: A possible object cycle was detected which is not supported. This can either be due to a cycle or if the object depth is larger than the maximum allowed depth of 0.

This is the same problem I wrote about in this article about Newtonsoft’s object cycle exception, except in this case it’s using System.Text.Json.JsonSerializer instead of Newtonsoft. The possible solutions are similar to the ones shown in that article, but not exactly the same.

First, what is an object cycle? Serializers work by recursively walking through an object’s properties. When it encounters a reference to an object that it already encountered, this means there’s a cycle. The serializer has to deal with this cycle, otherwise it would recurse infinitely and eventually get a StackOverflowException. The JsonSerializer’s default strategy for dealing with cycles is to throw an exception.

Here’s an example of an object with a circular reference. The Child class refers to the Parent class, which refers to the Child class:

Parent harry = new Parent() { Name = "Harry" }; Parent mary = new Parent() { Name = "Mary" }; harry.Children = new List<Child>() { new Child() { Name = "Barry", Dad=harry, Mom=mary } }; mary.Children = harry.Children; var json = JsonSerializer.Serialize(harry, new JsonSerializerOptions() { WriteIndented = true }); Console.WriteLine(json);
Code language: C# (cs)

Due to the circular reference, the call to JsonSerializer.Serialize() will throw the “object cycle detected” JsonException.

In this article I’ll show five different options for solving this problem. Pick the option that makes the most sense in your specific scenario.

Option 1 – Use the JsonIgnore attribute to make the serializer ignore the property with the circular reference

Put the JsonIgnore attribute on the properties with the circular references. This tells the serializer to not attempt to serialize these properties.

public class Child { [System.Text.Json.Serialization.JsonIgnore] public Parent Mom { get; set; } [System.Text.Json.Serialization.JsonIgnore] public Parent Dad { get; set; } public string Name { get; set; } }
Code language: C# (cs)

The resulting JSON looks like this:

{ "Children": [{ "Name": "Barry" }], "Name": "Harry" }
Code language: JSON / JSON with Comments (json)

If you choose to not serialize this information, the other side may have trouble deserializing, because the Mom/Dad properties are null.

Option 2 – Remove the circular reference

You may have accidently created this circular reference, or perhaps the property isn’t important to you. In either case, the solution is simple: remove the property.

Exception properties are a common cause of this problem. In this example, I have a Message class with an Exception property.

public class Message { public string Name { get; set; } public Exception Exception { get; set; } public void Throw() { throw new Exception(); } }
Code language: C# (cs)

Next, I’m going to throw an exception, stick it on an object, and try to serialize it:

try { var msg = new Message() { Name = "hello world" }; msg.Throw(); } catch (Exception ex) { var errorMessage = new Message() { Name = "Error", Exception = ex }; var json = JsonSerializer.Serialize(errorMessage, new JsonSerializerOptions() { WriteIndented = true }); Console.WriteLine(json); }
Code language: C# (cs)

This results in the circular reference exception.

I can solve by removing the Exception property. Instead, I’ll add a string property to hold the Exception message.

public class Message { public string Name { get; set; } public string ExceptionMessage { get; set; } public void Throw() { throw new Exception(); } }
Code language: C# (cs)

Option 3 – Use Newtonsoft instead and use ReferenceLoopHandling.Ignore

System.Text.Json.JsonSerializer doesn’t have a way to simply ignore circular references, whereas Newtonsoft does.

First, add the Newtonsoft.Json nuget package. This is using the Package Manager Console:

Install-Package Newtonsoft.Json
Code language: PowerShell (powershell)

Then use JsonConvert.SerializeObject() and pass in the ReferenceLoopHandling.Ignore option:

using Newtonsoft.Json; var json = JsonConvert.SerializeObject(harry, Formatting.Indented, new JsonSerializerSettings() { ReferenceLoopHandling = ReferenceLoopHandling.Ignore });
Code language: C# (cs)

The resulting JSON looks like this:

{ "Children": [ { "Mom": { "Name": "Mary" }, "Name": "Barry" } ], "Name": "Harry" }
Code language: JSON / JSON with Comments (json)

Option 4 – Create a JsonConverter to customize how the problematic object is serialized

Let’s say you want to solve this circular reference problem without having to change the classes that you’re serializing. These may even be third-party classes that you can’t change. In any case, you can customize the serialization of any object by subclassing JsonConverter and controlling the serialization for that object.

First, add a JsonConverter subclass, like this:

public class ChildJsonConverter : JsonConverter<Child> { public override bool CanConvert(Type objectType) { return objectType == typeof(Child); } public override Child Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return null; //Doesn't handle deserializing } public override void Write(Utf8JsonWriter writer, Child value, JsonSerializerOptions options) { writer.WriteStartObject(); writer.WriteString(nameof(value.Name), value.Name); writer.WriteString(nameof(value.Mom), value.Mom?.Name); writer.WriteString(nameof(value.Dad), value.Dad?.Name); writer.WriteEndObject(); } }
Code language: C# (cs)

Then use this converter by passing it into the JsonSerializerOptions.Converters list like this:

var options = new JsonSerializerOptions() { WriteIndented = true }; options.Converters.Add(new ChildJsonConverter()); var json = JsonSerializer.Serialize(harry, options);
Code language: C# (cs)

This outputs the following JSON:

{ "Children": [ { "Name": "Barry", "Mom": "Mary", "Dad": "Harry" } ], "Name": "Harry" }
Code language: JSON / JSON with Comments (json)

Option 5 – Use option ReferenceHandler.Preserve (in .NET 5)

Starting in .NET 5, they added the ReferenceHandler property to JsonSerializerOption.

You can use it like this:

var json = JsonSerializer.Serialize(harry, new JsonSerializerOptions() { WriteIndented = true, ReferenceHandler = ReferenceHandler.Preserve });
Code language: C# (cs)

When you serialize, it adds metadata properties to the JSON. So it looks like this:

{ "$id": "1", "Children": { "$id": "2", "$values": [ { "$id": "3", "Mom": { "$id": "4", "Children": { "$ref": "2" }, "Name": "Mary" }, "Dad": { "$ref": "1" }, "Name": "Barry" } ] }, "Name": "Harry" }
Code language: JSON / JSON with Comments (json)

This JSON has metadata properties. As long as the deserializer knows how to deal with metadata properties, then it’s not a problem.

Newtonsoft handles metadata properties by default, whereas with System.Text.Json you must specify the ReferenceHandler property when you’re deserializing:

var parent = Newtonsoft.Json.JsonConvert.DeserializeObject<Parent>(json); var parent2 = JsonSerializer.Deserialize<Parent>(json, new JsonSerializerOptions() { ReferenceHandler = ReferenceHandler.Preserve });
Code language: C# (cs)

If you don’t specify ReferenceHandler.Preserve here, you’ll get the following exception:

System.Text.Json.JsonException: The JSON value could not be converted

If you’re going to use this option to deal with circular references, make sure the deserializer knows how to deal with metadata properties appropriately.

2 thoughts on “System.Text.Json.JsonException: A possible object cycle was detected which is not supported”

  1. Option 5: By specifying Preserve for deserialization I avoided “JsonException: The JSON value could not be converted” but all of the received objects have their properties empty (i.e. default(T)). Any thoughts on that?

    Reply
    • Hi Jens,

      I’d guess there’s a property name casing mismatch. By default, JsonSerializer is case-sensitive, so if the received JSON has camelCasing and your class has PascalCasing, it will leave the values empty.

      Try using using “PropertyNameCaseInsensitive = true” in the options, like this:

      var parent2 = JsonSerializer.Deserialize(json, new JsonSerializerOptions()
      {
      ReferenceHandler = ReferenceHandler.Preserve,
      PropertyNameCaseInsensitive = true
      });

      If it still doesn’t work, we can discuss details by email if you want.

      Reply

Leave a Comment