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!

 

11 thoughts on “Fixing JSON Self Referencing Loop Exceptions”

  1. Hi, that’s right, if a developer writes code correctly, he will never get a link loop. It is really easy to solve using ViewModel.
    Thanks for the post, it was interesting to read and remind myself of some points

    Reply
  2. i was struggling with this for several hours today. seems like my view-model implementation was wrong
    thank u for explanation. awesome blog btw

    Reply
  3. Just wondering, you show us to make a viewmodel, but how to use it with EF Core?
    I try to understand, but I don’t really see it right away, I still have context.StaffMember, if I would use context.StaffMemberViewModel, it makes you wonder why I need the StaffMember class still.

    Sorry if I come over like a total beginner, but I only see part of the solution, not the whole story.

    Thank you!

    Reply
    • Not a total beginner question! One that gets asked a lot.

      So firstly, it shouldn’t be context.StaffMemberViewModel. ViewModels should not be inside the context at all, they should just live inside your web project.

      But more importantly, it gives you flexibility. If your question is, why do I have a StaffMemberViewModel and a StaffMember class when the properties are identical (Or almost identical). That’s only for the time being. If you added a new property to the StaffMember class in the Context/Database, if you don’t have a ViewModel you *instantly* expose that new property as part of your API contract. You have no choice since it’s the same model. But if you use a ViewModel, you can pick and choose which properties you expose.

      Reply
  4. I was trying to figure out why this was happening, your article here helped me understand it cleanly + I was able to solve the nasty little problem under 15mins 🙂 ( I straight up flipped off and shut my laptop on this last night lol)
    Anyway thanks for this 😀
    And Great Blog!
    Good Day!

    Reply
  5. Hi Wade, thanks for writing this article! It was very well written and easy to understand and the only thing I’d found that really articulated the situation really well. You commented “I will update this post when System.Text.Json gets it’s act together” and I’ve found at least one other article that stated there is suppose to be a solution for this in the System.Text.Json preview package version 5.0.0-alpha.1.20071.1 from dotnet5 gallery but even if there is I was not able to follow along with the article about how to get it to work so I wanted to find out if you know anything about this? Thank you sir!

    Reply
    • Sorry, to be brutally honest I just know and like NewtonsoftJson, so I’ve literally overridden System.Text.Json in every project so far. Not because I think System.Text.Json is bad, but because I know Newtonsoft well and don’t see any particular reason to switch (yet).

      Reply
  6. Update: It looks to me like the solution in the latest version of System.Text.Json is to add the following code in your Startup.ConfigureServices function in the Server project but I’m still getting the error:

    services.AddControllersWithViews().AddJsonOptions(options =>
    {
        options.JsonSerializerOptions.ReferenceHandler = System.Text.Json.Serialization.ReferenceHandler.Preserve;
    });
    
    Reply
  7. The System.Text.Json stuff in .NET 5.0 has finally caught up with Newtonsoft and will handle references up to 64 levels deep (ask me how I know 🙂 ), but event that can be expanded. I used Newtonsoft for the longest time because it could handle cyclic object graphs, but have switched over, mostly so I have one less third-party library to worry about. My web application gets database info using EF, converts that to an internal data model (with cyclic relationships) that gets cached in Session state, which is then used to generate view models as needed. It’s not necessarily bad design to have an object graph with cycles. It depends the needs of your design (c.f. Entity Framework). So it’s reasonable, I think, to expect serializers to handle cycles properly. I have seen too many posts on the subject that basically tell the developer, you need to change your design, not because the serialization library is missing an obvious feature, but because “your design is bad”. Sure, if the serializer isn’t going to work for you, you have to adjust, but it’s not because the object graph design is necessarily bad, and I think folks need to stop saying that. I needed to be able to serialize to JSON to store my data model in Session state. I used Newtonsoft to do that because my design had cycles and I needed the serializer that could deal with that. Now that the MS serializer has matured, I use that. YMMV.

    Reply

Leave a Comment