199

I have 2 entities that are related as one to many

public class Restaurant {
   public int RestaurantId {get;set;}
   public string Name {get;set;}
   public List<Reservation> Reservations {get;set;}
   ...
}
public class Reservation{
   public int ReservationId {get;set;}
   public int RestaurantId {get;set;}
   public Restaurant Restaurant {get;set;}
}

If I try to get restaurants with reservations using my api

   var restaurants =  await _dbContext.Restaurants
                .AsNoTracking()
                .AsQueryable()
                .Include(m => m.Reservations).ToListAsync();
    .....

I receive error in response, because objects contain references to each other. There are related posts that recommend to create separate model or add NewtonsoftJson configuration

Problem is that I do not want to create separate model and 2nd suggestion didn't help. Is there any way to load data without cycled relationship ? *

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 32. at System.Text.Json.ThrowHelper.ThrowInvalidOperationException_SerializerCycleDetected(Int32 maxDepth) at System.Text.Json.JsonSerializer.Write(Utf8JsonWriter writer, Int32 originalWriterDepth, Int32 flushThreshold, JsonSerializerOptions options, WriteStack& state) at System.Text.Json.JsonSerializer.WriteAsyncCore(Stream utf8Json, Object value, Type inputType, JsonSerializerOptions options, CancellationToken cancellationToken) at Microsoft.AspNetCore.Mvc.Formatters.SystemTextJsonOutputFormatter.WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding) at Microsoft.AspNetCore.Mvc.Formatters.SystemTextJsonOutputFormatter.WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding) at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Awaited|29_0[TFilter,TFilterAsync](ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted) at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResultExecutedContextSealed context) at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.ResultNext[TFilter,TFilterAsync](State& next, Scope& scope, Object& state, Boolean& isCompleted) at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.InvokeResultFilters()

*

Nitika Chopra
  • 1,281
  • 17
  • 22
Nazar Pylyp
  • 2,093
  • 2
  • 8
  • 7
  • 1
    Ask it to ignore the Restaurant property of the Reservation class. – Lasse V. Karlsen Dec 05 '19 at 16:37
  • 40
    Really you shouldn't be returning your DB entities directly from your API. I'd suggest creating API-specific DTOs and mapping accordingly. Granted you said you didn't want to do that but I'd consider it general good practice to keep API and persistence internals separate. – mackie Dec 05 '19 at 16:40
  • 5
    "Problem is that I do not want to create separate model". Your design is fundamentally flawed unless you do just that. An API is a contract like an interface (it's literally an application programming *interface*). It should not ever change, once published, and any change necessitates a new version, which will need to run concurrently with the old version (which will be deprecated and eventually removed in the future). That allows clients time to update their implementations. If you return an entity directly, you're tightly coupling your data layer. – Chris Pratt Dec 05 '19 at 19:04
  • 3
    Any change to that data layer then necessitates an immediate and irreversible change to the API, breaking all clients immediately until they update their implementations. In case it's not obvious, that's a bad thing. In short: never accept or return entities from an API. You should *always* use DTOs. – Chris Pratt Dec 05 '19 at 19:06
  • I'm using 'Request' and 'Response' models (I guess it's the same as DTO) managed by Automapper to prevent passing sensitive information and creating, as mentioned, contracts. Maybe I'm wrong, but DTOs do not solve this problem as I still have to return, restaurant where are reservations (with cycled reference). Anyway, thanks for answers ! :) – Nazar Pylyp Dec 06 '19 at 10:39
  • 26
    "You should always use DTOs" ... well, no, not always. If the only subscriber to your API is your SPA client application - why would I waste time writing DTOs if there is nothing to brake since the only subscriber has to be updated immediately as well. Best practices are fine, but I do not like "always". – mariob Nov 10 '20 at 11:50

21 Answers21

250

I have tried your code in a new project and the second way seems to work well after installing the package Microsoft.AspNetCore.Mvc.NewtonsoftJson firstly for 3.0

services.AddControllersWithViews()
    .AddNewtonsoftJson(options =>
    options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore
);

Try with a new project and compare the differences.

Ryan
  • 19,118
  • 10
  • 37
  • 53
  • 1
    Key moment here is to reinstalling proper version of Microsoft.AspNetCore.Mvc.NewtonsoftJson I didn't pay attention for version as this package was available under the box without any errors and warnings! Thanks for answer ! Everything works exactly as I expected ! – Nazar Pylyp Dec 06 '19 at 11:44
  • 15
    Isn't it wrong that with improved perf of the system json, we have to use NewtonsoftJson? :/ – Marek Urbanowicz Apr 04 '20 at 16:52
  • Thank you @Ryan :) – Rahul Aug 25 '20 at 21:37
  • 3
    @MarekUrbanowicz Its wrong to call a serializer which can't handle cycles, or dictionary types production ready. – Cristian E. Sep 02 '20 at 14:09
  • I could kiss you right now. I've been struggling with this for about a week, thanks. – Gonzalo Apr 05 '21 at 23:41
  • Newtonsoft still seems to be the better option – Rafat Rashid Jun 15 '21 at 11:50
  • 1
    After so much headache I finally found this!! Thank you so much it worked perfectly. Using a `JsonIgnore` attribute also worked but it's so inconvenient and no objects would be referenced at all which really sucked. But this is the perfect way to solve it. Thanks @Ryan! – baltermia Oct 14 '21 at 14:19
  • 7
    Starting with .NET 5 System.Text.Json contains the option [`ReferenceHandler.Preserve`](https://learn.microsoft.com/dotnet/api/system.text.json.serialization.referencehandler.preserve?view=net-6.0) and .NET 6 contains [`ReferenceHandler.IgnoreCycles`](https://learn.microsoft.com/dotnet/api/system.text.json.serialization.referencehandler.ignorecycles?view=net-6.0); both are very good alternatives to avoid switching back to Newtonsoft.Json and keep the performance gains of System.Text.Json. – Jozkee Oct 25 '21 at 07:15
  • This fixed the same problem I have. But, is this an ugly workaround or a must do? – YSFKBDY Nov 17 '21 at 13:40
  • 1
    +1 for @Jozkee's comment. I came here looking for this from a .NET 5 implementation and we're trying to avoid using the Newtonsoft.Json package and keep the new System.Text.Json. Solved my problem, thanks! It should be one of the answers, honestly. – DeVil Mar 25 '22 at 03:42
187

.NET Core 3.1 Install the package Microsoft.AspNetCore.Mvc.NewtonsoftJson (from https://www.nuget.org/packages/Microsoft.AspNetCore.Mvc.NewtonsoftJson/ )

Startup.cs Add service

services.AddControllers().AddNewtonsoftJson(options =>
    options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore
);
granadaCoder
  • 26,328
  • 10
  • 113
  • 146
anjoe
  • 1,871
  • 1
  • 4
  • 2
108

Who are still facing this issue: check if you await-ed all async methods.

Mitulát báti
  • 2,086
  • 5
  • 23
  • 37
53

Update:

Using .NET 6 there is an option for System.Text.Json to Ignore circular references like this:

JsonSerializerOptions options = new()
{
    ReferenceHandler = ReferenceHandler.IgnoreCycles,
    WriteIndented = true
};

https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-preserve-references?pivots=dotnet-6-0#ignore-circular-references

The problem with ReferenceHandler.Preserve is that JSON keys are prefixed with $ and this can cause some issues.

Example System.Text.Json ReferenceHandler.IgnoreCycles:

using System;
using System.Collections.Generic;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace SerializeIgnoreCycles
{
    public class Employee
    {
        public string Name { get; set; }
        public Employee Manager { get; set; }
        public List<Employee> DirectReports { get; set; }
    }

    public class Program
    {
        public static void Main()
        {
            Employee tyler = new()
            {
                Name = "Tyler Stein"
            };

            Employee adrian = new()
            {
                Name = "Adrian King"
            };

            tyler.DirectReports = new List<Employee> { adrian };
            adrian.Manager = tyler;

            JsonSerializerOptions options = new()
            {
                ReferenceHandler = ReferenceHandler.IgnoreCycles,
                WriteIndented = true
            };

            string tylerJson = JsonSerializer.Serialize(tyler, options);
            Console.WriteLine($"Tyler serialized:\n{tylerJson}");

            Employee tylerDeserialized =
                JsonSerializer.Deserialize<Employee>(tylerJson, options);

            Console.WriteLine(
                "Tyler is manager of Tyler's first direct report: ");
            Console.WriteLine(
                tylerDeserialized.DirectReports[0].Manager == tylerDeserialized);
        }
    }
}

// Produces output like the following example:
//
//Tyler serialized:
//{
//  "Name": "Tyler Stein",
//  "Manager": null,
//  "DirectReports": [
//    {
//      "Name": "Adrian King",
//      "Manager": null,
//      "DirectReports": null
//    }
//  ]
//}
//Tyler is manager of Tyler's first direct report:
//False

Source:

https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-preserve-references?pivots=dotnet-6-0#ignore-circular-references

Example with Newtonsoft.Json.ReferenceLoopHandling.Ignore

public class Employee
{
    public string Name { get; set; }
    public Employee Manager { get; set; }
}

Employee joe = new Employee { Name = "Joe User" };
Employee mike = new Employee { Name = "Mike Manager" };
joe.Manager = mike;
mike.Manager = mike;

string json = JsonConvert.SerializeObject(joe, Formatting.Indented, new JsonSerializerSettings
{
    ReferenceLoopHandling = ReferenceLoopHandling.Ignore
});

Console.WriteLine(json);
// {
//   "Name": "Joe User",
//   "Manager": {
//     "Name": "Mike Manager"
//   }
// }

https://www.newtonsoft.com/json/help/html/ReferenceLoopHandlingIgnore.htm

Original:

I got this error from default POST method in Controller created with API Controller with actions, using entity framework.

return CreatedAtAction("GetLearningObjective", new { id = learningObjective.Id }, learningObjective);

System.Text.Json.JsonException: A possible object cycle was detected. This can either be due to a cycle or if the object depth is larger than the maximum allowed depth of 32. Consider using ReferenceHandler.Preserve on JsonSerializerOptions to support cycles. at System.Text.Json.ThrowHelper.ThrowJsonException_SerializerCycleDetected(Int32 maxDepth)

When calling HttpGet directly from Postman or browser it worked without a problem. Solved by editing Startup.cs - services.AddControllers() like this:

services.AddControllers().AddJsonOptions(options =>
{
    options.JsonSerializerOptions.ReferenceHandler = ReferenceHandler.Preserve;
});

You could also solve it like this:

services.AddControllers(options =>
{
    options.OutputFormatters.RemoveType<SystemTextJsonOutputFormatter>();
    options.OutputFormatters.Add(new SystemTextJsonOutputFormatter(new JsonSerializerOptions(JsonSerializerDefaults.Web)
    {
        ReferenceHandler = ReferenceHandler.Preserve,
    }));
});

https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-preserve-references?pivots=dotnet-5-0

Ogglas
  • 62,132
  • 37
  • 328
  • 418
33

Instead of using NewtonsoftJson I used System.Text.Json.Serialization

For .Net Core 3.1

In Startup.cs

 public void ConfigureServices(IServiceCollection services)
 {
    ..........
    .......

    services.AddControllers().AddJsonOptions(options =>
    {
        options.JsonSerializerOptions.ReferenceHandler = ReferenceHandler.IgnoreCycles;
        options.JsonSerializerOptions.WriteIndented = true;
    });
 }

For .Net 6

In Program.cs

builder.Services.AddControllers().AddJsonOptions(options => 
{ 
    options.JsonSerializerOptions.ReferenceHandler = ReferenceHandler.IgnoreCycles;
    options.JsonSerializerOptions.WriteIndented = true;
});
Abi Chhetri
  • 1,131
  • 10
  • 15
24

Getting the setting JSON serialisation options on startup to work is probably a preferred way as you will likely have similar cases in the future. In the meantime however you could try add data attributes to your model so it's not serialised: https://www.newtonsoft.com/json/help/html/PropertyJsonIgnore.htm

public class Reservation{ 
    public int ReservationId {get;set;} 
    public int RestaurantId {get;set;} 
    [JsonIgnore]
    public Restaurant Restaurant {get;set;} 
}
timur
  • 14,239
  • 2
  • 11
  • 32
  • This works too. But as you mentioned, with this you have to update all the models, I prefer services.AddControllers().AddNewtonsoftJson(options => options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore ); – Nantharupan Feb 22 '20 at 06:48
  • That would be a situation where I would lead to think what if I don't want to use JsonIgnore because of other ways to access Reservation. – NETRookie Jan 18 '23 at 14:38
15

This worked using System.Text.Json

var options = new JsonSerializerOptions()
        {
            MaxDepth = 0,
            IgnoreNullValues = true,
            IgnoreReadOnlyProperties = true
        };

Using options to serialize

objstr = JsonSerializer.Serialize(obj,options);
IObote
  • 159
  • 1
  • 4
  • 4
    This sets MaxDepth but still does not allow objects with depths higher than 0 to go through. What we need is an ignore – uncommon_name Sep 12 '20 at 18:24
11

As @Jozkee stated in a comment in the accepted answer, .NET 6 contains ReferenceHandler.IgnoreCycles in System.Text.Json.

This is how I solved this issue without installing Newtonsoft.Json and making use of the new addition to .NET 6 by adding the following to Program.cs.


builder.Services.AddControllersWithViews()
    .AddJsonOptions(options => options.JsonSerializerOptions.ReferenceHandler = ReferenceHandler.IgnoreCycles);

Here's a helpful article on object cycles for anyone who's not sure what they are.

Vicktor
  • 169
  • 2
  • 10
9

After hours of debugging, it really has a simple solution. I found this link helpful.

This error was because of:

default JSON serializer used in ASP.NET Core 3.0 and the above version.

ASP.NET Core 3.0 has removed the dependency on JSON.NET and uses it’s own JSON serializer i.e ‘System.Text.Json‘.

I was able to fix the issue adding the reference to NewtonsoftJson Nuget package,

PM> Install-Package Microsoft.AspNetCore.Mvc.NewtonsoftJson -Version 3.1.2

And update the Startup.cs as below,

services.AddControllers().AddNewtonsoftJson(options =>
    options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore
);

ReferenceLoopHandling is currently not supported in the System.Text.Json serializer

Pang
  • 9,564
  • 146
  • 81
  • 122
Arsalan Haider
  • 108
  • 2
  • 8
7
public class Reservation{ 
public int ReservationId {get;set;} 
public int RestaurantId {get;set;} 
[JsonIgnore]
public Restaurant Restaurant {get;set;} 

Above worked also. But I prefer the following

services.AddControllers().AddNewtonsoftJson(options =>
    options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore
);

Because first we need to add the attribute to all the models we may have the cyclic reference.

Nantharupan
  • 594
  • 3
  • 15
7

This happens because of the 2 way relationship between your data model when it comes to be JSON serialized.

You should not return your data model diectly. Map it to a new response model then return it.

A Houghton
  • 372
  • 5
  • 18
7

I got such an error when I mistakenly returned Task<object> instead of an object in the controller method. The task leads to a loop. Check what you are returning.

Yury Yatskov
  • 175
  • 2
  • 7
7

For others who did not find other solutions working, you actually need to analyze your complete call stack, and see if any async call is not awaited where it is expected to be awaited. Which is the actual issue in problem mentioned in question.

For example, consider following method in MyAppService, which calls an async Task<int> of MyOtherService:

public async Task<int> Create(InputModel input)
{
    var id = _myOtherService.CreateAndGetIdAsync(input);
    return Created("someUri", id);
}

If CreateAndGetIdAsync method is async Task, the call to this Create method above will through the given exception as mentioned in question. This is because serialization will break as id is Task<int> but not int in reality. So one must await before returing response.

Additional Note: It's important to note one more thing here, that even thought this exception arises, it doesn't impact the db operation. i.e, in my example above, the db operation will be successful. Similarly, as mentioned in OP, the exception wasn't thrown my ORM being used, but this exception was thrown later in sequence of call stack (in one of the callers).

Zeeshan
  • 2,884
  • 3
  • 28
  • 47
4

MinimalAPI use this:

builder.Services.Configure<Microsoft.AspNetCore.Http.Json.JsonOptions>(options =>
{
    options.SerializerOptions.ReferenceHandler = ReferenceHandler.IgnoreCycles;
});
Kenlly Acosta
  • 547
  • 5
  • 10
4

For .Net core 6 , adding this to Program.cs solved my problem

builder.Services.AddControllers().AddJsonOptions(x =>
                x.JsonSerializerOptions.ReferenceHandler = ReferenceHandler.IgnoreCycles);
4

Microsoft proposed solution

Because EF Core automatically does fix-up of navigation properties, you can end up with cycles in your object graph. For example, loading a blog and its related posts will result in a blog object that references a collection of posts. Each of those posts will have a reference back to the blog.

https://learn.microsoft.com/en-us/ef/core/querying/related-data/serialization

Json.NET

public void ConfigureServices(IServiceCollection services)
{
    ...

    services.AddMvc()
        .AddJsonOptions(
            options => options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore
        );

    ...
}

System.Text.Json

public void ConfigureServices(IServiceCollection services)
{
    ...

    services.AddControllers()
        .AddJsonOptions(options =>
        {
            options.JsonSerializerOptions.ReferenceHandler = ReferenceHandler.IgnoreCycles;
        });

    ...
}

My recommendation

Don't expose your DB models directly to your controllers, that might bring some vulnerabilities in the future. You can create a mapping from the Model to a Dto class or something similar. This way you will not need to disable the cycle-detection mechanism since it is useful to prevent issues.

FabioStein
  • 750
  • 7
  • 23
3

I know that this question was for .net Core 3.0, but for anyone encountering the same problem in .net 5.0, I found a solution here.

In summary, one needs to add the following code in your Startup class, in the ConfigureServices method:

services.AddControllers().AddJsonOptions(x =>
   x.JsonSerializerOptions.ReferenceHandler = ReferenceHandler.Preserve);
Hugo Nava Kopp
  • 2,906
  • 2
  • 23
  • 41
1

The accepted answer changes the default serializer from System.Text.Json to Newtonsoft and will solve the cycle by removing the navigation property from the serialization!

Order example:

{
   "id": "d310b004-79a2-4661-2f90-08d8d25fec03"
   "orderItems": [
      {
         "orderId": "d310b004-79a2-4661-2f90-08d8d25fec03",
         "orderItemId": "83d36eda-ba03-448c-e53c-08d8d25fec0b",
         "orderItem": {
            "id": "83d36eda-ba03-448c-e53c-08d8d25fec0b",
            "name": "My order item"
         }
         // where is the reference to the order?!
      }
   ]
}

If you don't want to change the default serializer or you need to preserve the navigation property you can configure System.Text.Json serializer to preserve the references. But be careful because it changes the output structure by providing $id, $ref and $values properties!

services.AddControllers().AddJsonOptions(options => options.JsonSerializerOptions.ReferenceHandler = ReferenceHandler.Preserve)

Order example:

{
   "$id": "1",
   "id": "d310b004-79a2-4661-2f90-08d8d25fec03"
   "orderItems": {
      $"id": "2",
      $"values": [
         {
            $"id": "3",
            "orderId": "d310b004-79a2-4661-2f90-08d8d25fec03",
            "orderItemId": "83d36eda-ba03-448c-e53c-08d8d25fec0b",
            "orderItem": {
               "id": "83d36eda-ba03-448c-e53c-08d8d25fec0b",
               "name": "My order item"
            },
            "order": {
               "$ref": "1" // reference to the order
            }
         }
      ]
   }
}
Lukáš Kmoch
  • 1,239
  • 11
  • 11
0

I came across this issue and I was confused because I have another application running the same code, only difference was I wanted to use await this time, and in the last application I used ConfigureAwait(false).GetAwaiter().GetResult();

So by removing await and adding ConfigureAwait(false).GetAwaiter().GetResult() at the end of the Async method I was able to resolve this.

MrLu
  • 376
  • 2
  • 9
  • This has nothing to do with the question. Maybe you posted to the wrong question? – Gert Arnold Nov 05 '20 at 13:16
  • I got the same error "possible object cycle was detected which is not supported" but maybe my issue was different than this one. – MrLu Nov 06 '20 at 02:31
0

I encountered this and I had to tell the app/context to ignore the parent entity on the child by adding the following to the OnModelCreating(ModelBuilder builder) method in my db context class:

builder.Entity<ChildEntity>()
    .HasOne(a => a.ParentEntity)
    .WithMany(m => m.ChildEntities);
builder.Entity<ChildEntity>().Ignore(a => a.ParentEntity);

The last line with the Ignore is what did it for me.

How 'bout a Fresca
  • 2,267
  • 1
  • 15
  • 26
  • 2
    That's quite a sledgehammer approach only to fix an object cycle in serialization. Now EF won't ever populate the navigation property. – Gert Arnold Dec 24 '20 at 14:13
  • It is brute force, but it works for me and has not had any ill effects. If a window on your house doesn't close properly, you can install a new window or you can also make the choice to wall over it if you don't want that window in your house anymore. The two are both perfectly valid depending on your scenario. Downvote seems excessive as it is a viable solution, even if it is not the one you would personally choose. – How 'bout a Fresca Apr 15 '21 at 14:31
  • Not my DV, only commented. – Gert Arnold Apr 15 '21 at 14:32
0

Try to add [JsonIgnore] annotation on your dependent attribute.

public class Reservation{
   public int ReservationId {get;set;}
   public int RestaurantId {get;set;}
   [JsonIgnore]
   public Restaurant Restaurant {get;set;}
}

https://medium.com/@panjunweide/one-notation-to-fix-object-cycle-was-detected-issue-jsonignore-dfb14f94075f

Sky Pan
  • 431
  • 4
  • 5