【问题标题】:How to JSON serialize without cyclic error如何在没有循环错误的情况下进行 JSON 序列化
【发布时间】:2020-12-06 01:15:29
【问题描述】:

我有两个实体,扬声器和会话,其中扬声器可以是多个会话的一部分,会话可以有多个扬声器。我已将它们定义如下,并且我认为已成功为扬声器表播种(我相信它有效,因为链接表 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; }

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

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

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

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

【问题讨论】:

  • 这个问题与Entity Framework无关。任何两个相互有公共引用的对象都会发生这种情况。
  • 您是否阅读过您发布的错误消息?它会在邮件末尾回答您的问题。
  • 是的@Flater,我确实看到了,但不知道如何设置该选项。当我用谷歌搜索它时,我发现很多对 NewtonSoft 的引用,我认为它们在 .net 5 中已经过时了。
  • @PeterKellner 那是因为在 .Net 5 之前没有办法仅使用 System.Text.Json(即没有 Newtonsoft.Json)来处理这个问题。这个问题/答案可能更合适:stackoverflow.com/questions/60197270/…

标签: c# asp.net-core


【解决方案1】:

为了澄清这个问题:发生错误是因为在 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 

但是在序列化过程中,当将这些对象实例表示为文本 (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...
            }
          ]
        }
      ]
    }
  ]
}

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

继续你的问题:

如何获得带有演讲者和会话详细信息的干净 JSON 输出

简短的回答是,目前不支持使用 System.Text.Json(在 .NET 5 中)简单地忽略对象循环。请参阅 this open GitHub issue 以关注它的请求,目前计划用于 .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; }
}

这完全消除了 API 中的对象循环,并准确地表示(我假设)您期望它的外观:

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

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": []
    }
  ]
}

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

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

这实际上看起来与选项 1 相同。

【讨论】:

    【解决方案2】:

    对于 ASP.NET CORE 5.0,我能够在 Startup.cs 中的 this documentation 之后实现正确的序列化:

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddControllersWithViews()
            .AddNewtonsoftJson(options =>
            {
                options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
            });
    }
    

    因此,客户端会收到 JSON 对象,而无需使用新的 ReferenceHandler 获得的任何其他元数据。

    【讨论】:

      【解决方案3】:

      这似乎可行,但我不太明白 MaxDepth 0 返回任何东西。我也不明白为什么这是必要的以及它如何改变处理。 @Flater 在导致我这样做的 cmets 中建议了它。

      services.AddControllersWithViews().AddJsonOptions(o =>
              {
                  o.JsonSerializerOptions.ReferenceHandler = ReferenceHandler.Preserve;
                  o.JsonSerializerOptions.MaxDepth = 0;
              });
      

      但是,我现在在序列化中获得了很多额外的数据,这将使解析这些数据变得非常有问题。

      有没有办法在没有所有额外的 $id、$value 类型数据的情况下获得干净的 JSON?

      {
        "$id": "1",
        "$values": [
          {
            "$id": "2",
            "id": 620,
            "first": "Ron",
            "last": "Kleinman",
            "bio": "Ron teaches Object Oriented Analysis and Design at De Anza College ",
            "favorite": false,
            "twitterHandle": null,
            "company": "De Anza College",
            "sessions": {
              "$id": "3",
              "$values": [
                {
                  "$id": "4",
                  "id": 86,
                  "title": "The Performance Limitations  of the Java Platform ... and how to avoid them",
                  "day": "Not Assigned",
                  "eventYear": "2008",
                  "level": "Intermediate",
                  "favorite": false,
                  "time": "Not Assigned",
                  "sessions": {
                    "$id": "5",
                    "$values": [
                      {
                        "$ref": "2"
                      }
                    ]
                  }
                },
      

      【讨论】:

      • 参见docs.microsoft.com/en-us/dotnet/api/…“在编写复杂的引用类型时,序列化程序还会在其中写入元数据属性($id、$values 和 $ref)。”
      • 谢谢@LeeTaylor,有没有办法省略这些数据,只得到反序列化的一级输出?
      • 据我所知,如果您省略 ReferenceHandler.Preserve 行,您将遇到数据的自引用性质问题。我认为,如果您想避免这些值,则必须展平数据并使用 ID 在反序列化后重建引用。
      猜你喜欢
      • 1970-01-01
      • 2021-03-31
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-10-25
      • 1970-01-01
      • 2023-03-03
      • 1970-01-01
      相关资源
      最近更新 更多