When you use System.Text.Json.JsonSerializer to serialize an object that has a cycle, you 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.
In .NET 6+, you can use the ReferenceHandler.IgnoreCycles option:
var json = JsonSerializer.Serialize(person, new JsonSerializerOptions()
{
ReferenceHandler = ReferenceHandler.IgnoreCycles
});
Code language: C# (cs)
This nulls out properties with circular references instead of throwing an exception.
In .NET 5, you can use the ReferenceHandler.Preserve option:
var json = JsonSerializer.Serialize(person, new JsonSerializerOptions()
{
ReferenceHandler = ReferenceHandler.Preserve
});
Code language: C# (cs)
Note: This results in JSON with metadata attributes (like “$id”: “1”).
If you’re on a previous version without these two ReferenceHandler options available, I’ll explain your options below.
Options before .NET 5 and 6
Option 1 – Use Newtonsoft with ReferenceLoopHandling.Ignore
You can use Newtonsoft with option ReferenceLoopHandling.Ignore. This makes it ignore circular references. This is equivalent to using ReferenceHandler.IgnoreCycles from System.Text.Json in .NET 6+.
First, add the Newtonsoft.Json 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 2 – Remove the property with 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 JsonIgnore on 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 4 – Create a custom JsonConverter
Another option for avoiding circular reference exceptions is to create a custom JsonConverter. This allows you to control serialization for a type. Circular references are only a problem when you don’t know the type you’re dealing with, so you must use reflection and recurse through its properties. With a custom converter, you know the type you’re dealing with, and therefore avoid this problem.
Here’s an example of using a custom converter. First, subclass JsonConverter:
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: 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?
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.