If you've stumbled upon this post it most likely means that you're experiencing the following error while trying to serialize an Entity Framework (or EF Core) IQueryable result into a JSON string:
JsonSerializationException: Self referencing loop detected with type '<ENTITY_TYPE>'.
Which usually appears in a typical Visual Studio debugger page like the following one:
As we can see by looking at the stack trace, we're using the Json.NET NuGet package, also known as Newtonsoft.Json, a popular high-performance JSON framework for .NET. Such library has been around since june 2006 and it's now a well-established framework for working with JSON, which hundreds of thousands of downloads by developers from around the world; more specifically, we're using Json.NET as a drop-in replacement of the ASP.NET Core's native System.Text.Json library following the instructions provided by this post. This basically means that New
However, if we were using the built-in System.Text.Json library, we would've faced a rather similar error:
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 32.
As shown in the following screenshot:
The two errors might seem different, but they are caused by the same issue: our Entity Framework Core result has one or more circular references, thus making the Json serializer engine (be it native or not) unable to properly do its job.
In the unlikely case we don't know what we're talking about: a circular reference happens when an object has one or more properties referencing one or more objects, which in turn contain one or more properties referencing other objects (and so on) that ultimately reference the first one or a "parent" one, thus resulting in a closed loop.
Here's a diagram that visually shows a typical circular reference scenario (source - Wikipedia):
Pretty neat, right? Now, it's pretty evident how the Json serializer can't come out of this mess - unless we explicitly tell it how to handle such kind of scenarios.
Luckily enough, this can be easily done by properly configuring the default serialization settings. The required changes depends on the JSON APi you're currently using.
Newtonsoft's Json.NET
If you're using Newtonsoft.Json as a drop-in replacement of your app's default Json serializer, you need to add the following highlighted line of code in the ConfigureServices() method of the Startup.cs file, which is located in your ASP.NET Core project root folder:
1 2 3 4 5 6 7 8 9 |
services.AddControllersWithViews(options => { options.Filters.Add(new AutoValidateAntiforgeryTokenAttribute()); }) .AddViewLocalization(LanguageViewLocationExpanderFormat.Suffix) .AddDataAnnotationsLocalization() .AddRazorRuntimeCompilation() .AddNewtonsoftJson(options => { options.SerializerSettings.Formatting = Formatting.Indented; }); |
It's worth noting that you might have a slightly different configuration pattern depending on your project's settings and type: for example, instead of AddControllersWithViews() you could have AddControllers(), AddMvc() or AddRazorPages() (see this post for more details): regardless of that, the options.SerializerSettings.Formatting = Formatting.Indented setting is what you need to add to fix your issue.
If you're using Newtonsoft.Json as a separate package, such as in the following way...
1 2 3 4 5 6 |
[HttpGet] public async Task<IActionResult> Index() { var obj = new AnySerializableObject("with-some-params"); return Content(JsonConvert.SerializeObject(obj), "application/json"); } |
... you won't be able to globally setup its settings like we just did.
If that's the case, the best thing we can do is to define a custom JsonSerializerSettings object and then pass it out to the JsonConvert.SerializeObject() method in the following way:
1 2 3 4 5 6 7 8 9 10 |
[HttpGet] public async Task<IActionResult> Index() { var obj = new AnySerializableObject("with-some-params"); var settings = new JsonSerializerSettings() { ReferenceLoopHandling = ReferenceLoopHandling.Ignore }; return Content(JsonConvert.SerializeObject(obj), "application/json"); } |
It goes without saying that such settings variable can be instantiated either locally or globally, depending on our given scenario.
System.Text.Json
If we're using the Json serializer APIs provided by the built-in System.Text.Json namespace, we can perform a similar fix either globally (for all serializers):
1 2 3 4 5 6 7 8 9 |
services.AddControllersWithViews(options => { options.Filters.Add(new AutoValidateAntiforgeryTokenAttribute()); }) .AddViewLocalization(LanguageViewLocationExpanderFormat.Suffix) .AddDataAnnotationsLocalization() .AddRazorRuntimeCompilation() .AddJsonOptions(options => { options.JsonSerializerOptions.ReferenceHandling = ReferenceHandling.Preserve; }); |
or locally (for manual serializers):
1 2 3 4 5 |
var options = new JsonSerializerOptions { ReferenceHandling = ReferenceHandling.Preserve }; string json = JsonSerializer.Serialize(obj, options); |
However, the ReferenceHandling property has been introduced in System.Text.Json v5.0.0-rc.1, which is part of the new .NET 5 Framework that (at the time of writing) is still in prerelease: therefore, if you want to apply this fix, you'll need to include the prerelease packages in NuGet and install a non-GA version. For that very reason, we definitely suggest to switch to the Newtonsoft.Json package, at least for the time being, and use one of the workarounds described in the previous paragraph.
Conclusion
That's it: we hope that this post will help other ASP.NET developers who're struggling with these nasty JSON serialization errors and make them able to fix them. If you happen to be one of them, don't forget to leave your valuable feedback using the comments section below!