如何在没有循环错误的情况下进行 JSON 序列化

Pet*_*ner 0 c# asp.net-core

我有两个实体,Speakers 和 Sessions,其中演讲者可以是多个会话的一部分,会话可以有多个演讲者。我已经将它们定义如下,并且我认为已经成功地为扬声器表设置了种子(我相信它有效,因为链接表 SessionRecSpeakerRec 是按照我的预期创建的。

以下是我的定义:

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; }
Run Code Online (Sandbox Code Playgroud)

在我的 c# asp.net 控制器代码中,我这样做是为了获取带有会话的发言人列表:

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

我得到循环错误。关于为什么以及如何解决的任何线索?

JsonException: 检测到可能的对象循环。这可能是由于循环或对象深度大于最大允许深度 32。考虑在 JsonSerializerOptions 上使用 ReferenceHandler.Preserve 以支持循环。

dev*_*ull 5

澄清这个问题:发生错误是因为在 C# 土地中,您的实例在内存中相互引用

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 
Run Code Online (Sandbox Code Playgroud)

但是在序列化过程中,将这些对象实例表示为文本 (JSON) 时,序列化程序基本上会尝试执行以下操作:

{
  "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...
            }
          ]
        }
      ]
    }
  ]
}
Run Code Online (Sandbox Code Playgroud)

所以你得到JsonException: A possible object cycle was detected. 如您所见,在 .NET 5 中的 System.Text.Json SerializerOptions 中有一种方法可以处理此问题:ReferenceHandler.Preserve。但正如您也发现的,这种方法添加了元数据属性,以便将对象周期正确表示为 JSON。

那么关于你的问题:

如何获得带有扬声器和会话详细信息的干净 JSON 输出

简短的回答是,目前不支持使用 System.Text.Json(在 .NET 5 中)简单地忽略对象循环。请参阅此未解决的 GitHub 问题以遵循当前针对 .NET 6 的请求。

话虽如此,您处理这种情况的选择是(按照我个人推荐的顺序):

1 - 为 API 创建一个单独的对象层

这非常简单,您无需担心使用潜在循环序列化对象或使用序列化属性装饰数据对象。为此,您需要为数据对象创建相应的 DTO,映射它们,然后简单地返回它们:

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; }
}
Run Code Online (Sandbox Code Playgroud)

这完全消除了 API 中的对象循环,并准确表示(我假设)您希望它看起来如何:

{
  "Id": 1,
  "First": "Speaker",
  "Last": "Speaks",
  "Sessions": [
    {
      "Id": 1,
      "Title": "Session"
    }
  ]
}
Run Code Online (Sandbox Code Playgroud)

2 - 使用 Newtonsoft.Json

Newtonsoft.Json 确实对忽略对象循环(ReferenceLoopHandling)有开箱即用的支持:

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

{
  "Id": 1,
  "First": "Speaker",
  "Last": "Speaks",
  "Sessions": [
    {
      "Id": 1,
      "Title": "Session",
      "Speakers": []
    }
  ]
}
Run Code Online (Sandbox Code Playgroud)

3 - 通过修饰数据类忽略序列化属性

public class SessionRec
{
    public int Id { get; set; }
    public string Title { get; set; }
    [JsonIgnore]
    public virtual List<SpeakerRec> Speakers { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

这实际上与选项 1 相同。