Fixing JSON Self Referencing Loop Exceptions

I’ve debated about posting an article on this for a long long time. Mostly because I think if you code any C# MVC/API project correctly, you should almost never run into this issue, but I’ve seen people run into the problem over and over again. What issue? Well it’s when you end up with two very interesting exceptions. I say two because depending on whether you are using System.Text.Json or Newtonsoft.JSON in your API, you will get two different error messages.

For System.Text.Json (Default for .NET Core 3+), you would see something like :

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.

And for Newtonsoft.Json (Or JSON.NET as it’s sometimes called, default for .NET Core 2.2 and lower) :

JsonSerializationException: Self referencing loop detected with type

They mean essentially the same thing, that you have two models that reference each other and will cause an infinite loop of serializing doom.

Why Does This Happen?

Before we get into how to resolve the problem, let’s have a good dig into why this happens.

Let’s assume I have an API that contains the following models :

public class StaffMember
{
    public string FirstName { get; set; }
    public Department Department { get; set; }
}

public class Department
{
    public List StaffMembers { get; set; }
}

Already we can see that there is a small problem. The class StaffMember references Department, and Department references StaffMember. In normal C# code this isn’t such an issue because it’s simply pointers. But when we are working inside an API, when we output this model it has to traverse the full model to output our JSON model.

So if for example we had an API endpoint that looked like this :

[HttpGet]
public ActionResult Get()
{
    var staff = new StaffMember { FirstName = "John Smith" };
    var department = new Department();
    staff.Department = department;
    department.StaffMembers = new List { staff };

    return Ok(staff);
}

We are gonna blow up.

It takes our StaffMember to try and serialize, which points to the Department. It goes to the Department and tries to serialize it, and it finds a StaffMember. It follows that StaffMember and… goto:Step 1.

But you’re probably sitting there thinking “When have I ever created models that reference each other in such a way?!”. And you’d probably be pretty right. It’s exceedingly rare that you create these sorts of two way relationships… Except of course… when using Entity Framework relationships.

If these two classes were part of an EntityFramework model, it’s highly likely that it would look like so :

public class StaffMember
{
    public string FirstName { get; set; }
    public virtual Department Department { get; set; }
}

public class Department
{
    public virtual ICollection StaffMembers { get; set; }
}

In EntityFramework (Or many other ORMs), we create two way relationships because we want to be able to traverse models both ways, and often this can create reference loops.

The Real Solution

So putting “real” in the subtitle may trigger some because this isn’t so much a “here’s the line of code to fix this problem” solution in so much as it’s a “don’t do it in the first place”.

The actual problem with the above example is that we are returning a model (our datamodel) that isn’t fit to be serialized in the first place. In general terms, you *should not* be returning your data model direct from an API. In almost all cases, you should be returning a ViewModel from an API. And then when returning a view model, you wouldn’t make the same self referencing issue.

For example (Sorry for the long code)

[HttpGet]
public ActionResult Get()
{
    var staffMember = new StaffMember { Department = new Department() }; //(Really this should actually be calling a repository etc). 

    var viewModel = new StaffMemberViewModel
    {
        FirstName = staffMember.FirstName,
        Department = new StaffMemberViewModelDepartment
        {
            DepartmentName = staffMember.Department.DepartmentName
        }
    };

    return Ok(viewModel);
}

public class StaffMemberViewModel
{
    public string FirstName { get; set; }
    public StaffMemberViewModelDepartment Department { get; set; }
}

public class StaffMemberViewModelDepartment
{
    public string DepartmentName { get; set; }
}

public class StaffMember
{
    public string FirstName { get; set; }
    public virtual Department Department { get; set; }
}

public class Department
{
    public string DepartmentName { get; set; }
    public virtual ICollection StaffMembers { get; set; }
}

Here we can see we map the StaffMember data model into our fit for purpose ViewModel. It may seem overkill to create new models to get around this issue, but the reality is that this is best practice in any case. Not using ViewModels and instead returning your exact DataModel is actually going to cause a whole heap of other issues, so even if you solve the reference loop another way, you are still gonna have issues.

Global API Configuration Settings

So I’ve just ranted about how you really shouldn’t run into this issue if you use proper view models, but let’s say that you have to use a model that contains a reference loop. You have no other option, what can you do?

NewtonSoft.Json (JSON.NET)

Let’s start with if you are using Newtonsoft.Json first (If you are using .NET Core 3+, there is a guide on adding Newtonsoft back as the default JSON serializer here : https://dotnetcoretutorials.com/2019/12/19/using-newtonsoft-json-in-net-core-3-projects/).

You can then edit your startup.cs where you add in Newtonsoft to configure the ReferenceLoopHandling :

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllers().AddNewtonsoftJson(x => x.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore);
}

Now when Newtonsoft.Json runs into a loop, it simply stops serializing that tree. To me it’s still not pretty as your output essentially ends up like :

{"firstName":"John Smith","department":{"departmentName":null,"staffMembers":[]}}

Anyone reading this would at first think that the Department has no Staff Members, but in reality it just stopped serializing at that point because it detected it was about to loop.

Another thing to note is if you set the serializer to instead Serialize like so :

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllers().AddNewtonsoftJson(x => x.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Serialize);
}

Your website will just straight up crash. Locally, you will get an error like

The program '[23496] iisexpress.exe' has exited with code -1073741819 (0xc0000005) 'Access violation'.

But it’s essentially crashing because it’s looping forever (Like you’ve told it to!)

System.Text.Json

If you are using System.Text.Json, you are basically out of luck when it comes to reference loop support. There are numerous tickets created around this issue, but the actual feature is being tracked here : https://github.com/dotnet/runtime/issues/30820

Now the gist of it is, that the feature has been added to the serializer, but it’s not in the general release yet (Only preview release), and on top of that, even if you add the preview release to your project it actually doesn’t work as a global setting (See last comments on the issue). It only works if you manually create a serializer (For example serializing a model for an HttpClient call). So in general, if you are running into these issues and you don’t want to edit your ViewModel, for now it looks like you will have to use Newtonsoft.Json. I will update this post when System.Text.Json gets it’s act together!

Ignoring Properties

This one is weird because if you can access the model to make these changes, then just create a damn viewmodel! But in anycase, there is another way to avoid reference loops and that is to tell the serializer to not serialize a property at all. In *both* Newtonsoft.Json and System.Text.Json there is an attribute called JsonIgnore :

public class StaffMember
{
    public string FirstName { get; set; }
    public virtual Department Department { get; set; }
}

public class Department
{
    public string DepartmentName { get; set; }
    [JsonIgnore]
    public virtual ICollection StaffMembers { get; set; }
}

This means that the StaffMembers property on the Department object will simply not be output. It’s in my mind a slightly better option than above because you don’t see the property of StaffMembers with an empty array, instead you don’t see it at all!

 

ENJOY THIS POST?
Join over 3.000 subscribers who are receiving our weekly post digest, a roundup of this weeks blog posts.
We hate spam. Your email address will not be sold or shared with anyone else.

Leave a Reply

Your email address will not be published. Required fields are marked *