【问题标题】:System.Text.Json.JsonException: 'A possible object cycle was detected which is not supported....'System.Text.Json.JsonException:'检测到不支持的可能对象循环......'
【发布时间】:2020-07-06 15:11:52
【问题描述】:

我正在尝试序列化一个类,预期的行为是它成功了。它没有成功,标题中有错误。标题是错误的子集,因为完整的标题不适合。

这是完整的错误:

System.Text.Json.JsonException HResult=0x80131500 消息=A 检测到不支持的可能对象循环。这个可以 要么是由于一个循环,要么是物体深度大于 最大允许深度为 5。

我有一个无法序列化的非常简单的模型,并且使用 [JsonIgnore] 跳过属性的选项不可行。

类模型的样子;

Package 有一个属性 Steps,它是 Step 的 IList Step 有一个 Constraints 属性,它是一个 IList of Constraint。

当我尝试使用此代码进行序列化时;

    public static class PackageIO
    {
       public static void SaveAsJsonFile(Package pkg, string FullyQualifiedFileName)
       {
            string jsonString;

            //TODO: Needs Exception handler
            var options = new JsonSerializerOptions
            {
                WriteIndented = true,
                MaxDepth = 5
            };
            jsonString = JsonSerializer.Serialize(pkg, options);
            File.WriteAllText(FullyQualifiedFileName, jsonString);
       }
    }

我得到了例外。这是 .Net Core 3.1,并且该库不在网络应用程序中,因此我无法(轻松)切换到我有时建议的 MVC Newtonsoft 序列化程序。

如果我删除了上面的 Constraints 属性,那么它就可以序列化。这是 JSON 的样子;

{
  "Steps": [
    {
      "Name": "stepTestName"
    }
  ],
  "Name": "packageTestName"
}

这是包类的样子;

public class Package
{
    private string _name;
    private Steps<Step> _steps;
    public Package()
    {
        _steps = new Steps<Step>();
    }
    public Package(string name) : this()
    {
        _name = name;
    }
    public Steps<Step> Steps
    {
        get { return _steps; }
        set { _steps = value; }
    }
    public string Name
    {
        get { return _name; }
        set { _name = value; }
    }
}

Step 类如下所示;

public enum StepExecStatus
{
    Waiting = 1,
    InProgress = 2,
    Inactive = 3,
    Completed = 4
}

public class Step
{
    private string _name;
    private PrecedenceConstraints<PrecedenceConstraint> _precedenceConstraints;
    private StepExecStatus _execStatus;

    #region INTERNAL PROPERTIES
    internal StepExecStatus ExecStatus
    {
        get { return _execStatus; }
        set { _execStatus = value; }
    }
    #endregion

    #region INTERNAL METHODS
    internal StepExecStatus Execute()
    {
        return StepExecStatus.Completed;
    }

    #endregion

    #region PUBLIC PROPERTIES
    public string Name
    {
        get { return _name; }
        set { _name = value; }
    }

    public PrecedenceConstraints<PrecedenceConstraint> PrecedenceConstraints
    {
        get { return _precedenceConstraints; }
        set { _precedenceConstraints = value; }
    }
    #endregion

    #region PUBLIC METHODS
    public Step()
    {
        _precedenceConstraints = new PrecedenceConstraints<PrecedenceConstraint>();
        _execStatus = StepExecStatus.Waiting;
    }
    #endregion

}

下面是 Steps 集合的顶部,它现在只是一个基本的 IList 实现:

public class Steps<T> : IList<T> where T:Step
{
    private readonly List<T> _steps = new List<T>();

这里是约束类;

public enum StepPrecedenceValue
{
    Completion = 1,
    Success = 2,
    Failure = 3
}

public class PrecedenceConstraint
{
    private string _sourceStepName;
    private StepPrecedenceValue _constraintValue;
    private bool _constraintMet;

    public PrecedenceConstraint(string itemName, StepPrecedenceValue value)
    {
        _sourceStepName = itemName;
        _constraintValue = value;
    }

    public string SourceStepName
    {
        get { return _sourceStepName; }
        set { _sourceStepName = value; }
    }

    public StepPrecedenceValue ConstraintValue
    {
        get { return _constraintValue; }
        set { _constraintValue = value; }
    }

    public bool ConstraintMet
    {
        get { return GetConstraintMet(); }
        set { _constraintMet = value; }
    }

    private bool GetConstraintMet()
    {
        bool result = false;
        //TODO: Needs implemented

        return result;
    }

}

这里是 Constraints 类,现在又是一个基本的 IList 实现;

public class PrecedenceConstraints<T> : IList<T> where T:PrecedenceConstraint
{
    private readonly IList<T> _precedenceConstraints = new List<T>();

谢谢

【问题讨论】:

标签: c# json serialization


【解决方案1】:

正如其他人评论的那样,您需要发布您的约束/步骤类才能真正为您提供准确的答案,但我们可以非常确定导致问题的原因。

您的步骤类将引用一个约束,而该约束又将引用该步骤类或引用一个包。因此,当您要序列化您的对象时,您将有一个循环引用,因为它会逐步执行。

所以你的选择是:

  • 删除循环引用。例如不应该有两种方式的“导航”属性或类似的。 Package应该引用Step,Step应该引用Constraint,不能反其道而行之。
  • 如果您绝对需要代码中的逻辑能够以两种方式遍历对象,那么您可以在反向导航属性上使用[JsonIgnore] 属性,这样它们就不会被序列化。
  • 最后,您可以切换到使用 NewtonSoft 序列化程序(正如您已经提到的),因为它支持检测循环并且可以跳出循环并仍然序列化您的模型。
  • 目前,System.Text.Json 不支持处理循环引用 (https://github.com/dotnet/runtime/issues/30820) 的机制,因为实际上,它是对您的对象不容易序列化的事实的一个创可贴。

更多信息: https://dotnetcoretutorials.com/2020/03/15/fixing-json-self-referencing-loop-exceptions/

【讨论】:

  • 感谢所有建议。正如一些人指出的那样,最初的实现确实有一个来自 Step->Constraint->Step 的循环对象引用,但是当我第一次遇到上面的错误时,我将最后一步更改为使用名称,然后在幕后修复对象引用。所以它的 Step->Constraint->StepName.
【解决方案2】:

您在这里遇到了几个问题。

首先,你需要将MaxDepth5增加到6

var options = new JsonSerializerOptions
{
    WriteIndented = true,
    MaxDepth = 6 // Fixed
};
jsonString = JsonSerializer.Serialize(pkg, options);

演示小提琴 #1 here.

您尝试序列化的 JSON 如下所示:

{                                               // Level 1
  "Steps": [                                    // Level 2
    {                                           // Level 3
      "Name": "stepTestName",
      "PrecedenceConstraints": [                // Level 4
        {                                       // Level 5
          "SourceStepName": "stepTestName",     // THESE PROPERTY VALUES
          "ConstraintValue": 1,                 // ARE APPARENTLY LEVEL 6.
          "ConstraintMet": false
        }
      ]
    }
  ],
  "Name": "packageTestName"
}

似乎PrecedenceConstraints 对象中的原始属性值算作额外级别。如果我注释掉它的属性,我可以在MaxDepth = 5 序列化你的数据模型:

{
  "Steps": [
    {
      "Name": "stepTestName",
      "PrecedenceConstraints": [
        {} // No properties so level maxes out at 5, apparently.
      ]
    }
  ],
  "Name": "packageTestName"
}

演示小提琴#2 here 演示了这一点。 (documentation 没有解释MaxDepth 的确切含义。)

其次,您的PrecedenceConstraint 缺少公共的、无参数的构造函数。如文档 How to migrate from Newtonsoft.Json to System.Text.Json : Deserialize to immutable classes and structs 中所述,开箱即用不支持此类类型的反序列化:

System.Text.Json 仅支持公共无参数构造函数。作为一种解决方法,您可以在自定义转换器中调用带有参数的构造函数。

这会阻止您的数据模型成功反序列化。一种解决方法是根据文档的要求添加无参数构造函数:

public class PrecedenceConstraint
{
    private string _sourceStepName;
    private StepPrecedenceValue _constraintValue;
    private bool _constraintMet;

    public PrecedenceConstraint() { } // FIXED added parameterless constructor as required by System.Text.Json

    // Remainder unchanged.

现在您的数据模型可以在MaxDepth = 6 往返。演示小提琴 #3 here.

【讨论】:

  • 感谢这完美的工作,我必须学习如何使用小提琴作为其中的一部分!
【解决方案3】:

检查您是否已等待所有异步调用。

我遇到此错误的唯一一次是我忘记将 await 添加到函数调用并从我的 API 端点返回尚未执行的任务。

【讨论】:

    猜你喜欢
    • 2021-08-30
    • 2020-10-02
    • 2020-04-27
    • 2021-12-09
    • 2021-03-17
    • 2020-03-30
    • 2020-07-26
    • 2020-05-28
    • 1970-01-01
    相关资源
    最近更新 更多