Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to JSON serialize without cyclic error

I've got two entities, Speakers and Sessions where speakers can be part of multiple sessions and sessions can have multiple speakers. I've defined them as follows and have, I think, successfully seeded the speakers table (I believe it worked because a link table SessionRecSpeakerRec was created as I expected.

Here are my definitions:

public class SessionRec
{
    public int Id { get; set; }
    public string Title { get; set; }
    public virtual List<SpeakerRec> Sessions { get; set; }
}

public class SpeakerRec
{
    public int Id { get; set; }
    public string First { get; set; }
    public string Last { get; set; }
    public virtual List<SessionRec> Sessions { get; set; }
}

public DbSet<SessionRec> SessionRecs { get; set; }
public DbSet<SpeakerRec> SpeakerRecs { get; set; }

In my c# asp.net controller code, I do this trying to get a list of speakers with sessions:

[HttpGet]
 public IEnumerable<SpeakerRec> GetSpeakerRecs()
 {
        return _context.SpeakerRecs.Include(a=>a.Sessions).ToList();
 }

And I get the cycle error. Any clues as to why and how to fix?

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.

like image 898
Peter Kellner Avatar asked Jun 06 '26 06:06

Peter Kellner


1 Answers

To clarify the issue: the error is occurring because in C# land your instances have references to each other in memory:

var session = new SessionRec
{
    Id = 1,
    Title = "Session",
    Speakers = new List<SpeakerRec>()
};
var speaker = new SpeakerRec
{
    Id = 1,
    First = "Speaker",
    Last = "Speaks",
    Sessions = new List<SessionRec>()
};
session.Speakers.Add(speaker);
speaker.Sessions.Add(session);
// session.Speakers[0] == speaker 
// speaker.Sessions[0] == session 

But during serialization when representing these object instances as text (JSON), the serializer will essentially try to do the following:

{
  "Id": 1,
  "First": "Speaker",
  "Last": "Speaks",
  "Sessions": [
    {
      "Id": 1,
      "Title": "Session"
      "Speakers": [
        {
          "Id": 1,
          "First": "Speaker",
          "Last": "Speaks",
          "Sessions": [
            {
              "Id": 1,
              "Title": "Session",
              // You see where this is going...
            }
          ]
        }
      ]
    }
  ]
}

And so you get JsonException: A possible object cycle was detected. As you found, there is one way to handle this in the System.Text.Json SerializerOptions in .NET 5: ReferenceHandler.Preserve. But as you also found, this approach adds metadata properties in order to properly represent the object cycle as JSON.

So on to your question:

how I can get a clean JSON output with speakers and session details

The short answer is that there is currently no support to to simply ignore object cycles with System.Text.Json (in .NET 5). See this open GitHub issue to follow the request for it, which is currently slated for .NET 6.

With that said, your options to handle this scenario are (in order of what I would personally recommend):

1 - Create a separate object layer for the API

This one's pretty simple, and you don't need to worry about serializing objects with potential cycles or decorating your data objects with serialization attributes. For this, you'd create corresponding DTOs for your data objects, map them, and simply return them:

public class SessionRecDto
{
    public int Id { get; set; }
    public string Title { get; set; }
}

public class SpeakerRecDto
{
    public int Id { get; set; }
    public string First { get; set; }
    public string Last { get; set; }
    public List<SessionRecDto> Sessions { get; set; }
}

This entirely removes the object cycle in the API and represents exactly how (I assume) you expect it to look:

{
  "Id": 1,
  "First": "Speaker",
  "Last": "Speaks",
  "Sessions": [
    {
      "Id": 1,
      "Title": "Session"
    }
  ]
}

2 - Use Newtonsoft.Json

Newtonsoft.Json does have out of the box support for ignoring object cycles (ReferenceLoopHandling):

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

{
  "Id": 1,
  "First": "Speaker",
  "Last": "Speaks",
  "Sessions": [
    {
      "Id": 1,
      "Title": "Session",
      "Speakers": []
    }
  ]
}

3 - Ignore serializing the property by decorating the data class

public class SessionRec
{
    public int Id { get; set; }
    public string Title { get; set; }
    [JsonIgnore]
    public virtual List<SpeakerRec> Speakers { get; set; }
}

This will effectively look the same as option 1.

like image 162
devNull Avatar answered Jun 07 '26 18:06

devNull



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!