简体   繁体   中英

System.Text.Json.JsonException: 'A possible object cycle was detected which is not supported....'

I am trying to serialize a class, expected behaviour is that it succeeds. It does not succeed with error in the title. The title is a subset of the error as the full one will not fit.

Here is the full error:

System.Text.Json.JsonException HResult=0x80131500 Message=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 5.

I have a pretty simple model that I am unable to serialize and the option to skip properties using [JsonIgnore] is not viable.

The class model looks like;

Package has a property Steps which is an IList of Step Step has a property of Constraints which is an IList of Constraint.

When I try and serialize using this code;

    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);
       }
    }

I get the exception. This is .Net Core 3.1 and the library is not in a web app so I can't (easily) switch to the MVC Newtonsoft serializer that I see suggested sometimes.

If I remove the Constraints property above then it serializes just fine. Here is what the JSON looks like;

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

Here is what the package class looks like;

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; }
    }
}

Here is what the Step class looks like;

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

}

Here is what the top of the Steps collection looks like its just a basic IList implementation for now:

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

Here is the constraint class;

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;
    }

}

And here is the Constraints class again a basic IList implementation for now;

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

Thx

As others have commented, you will need to post your constraint/step class to really give you an exact answer, but we can be pretty certain what will be causing the issue.

Your step class will reference a constraint, which in turn will either reference the step class or reference a package. So you will have a circular reference when coming to serialize your object because as it steps through.

So your options are :

  • Remove the circular reference. eg there shouldn't be two way "navigation" properties or similar. Package should reference Step, Step should reference Constraint, and you can't go the other way.
  • If you absolutely need the logic in your code to be able to traverse through the objects both ways, then you can use the [JsonIgnore] attribute on the navigation properties that go in reverse so that they aren't serialized.
  • Finally, you can switch to using NewtonSoft serializer (As you already mentioned), as that has support for detecting looping and can break out of the loop and still serialize your model.
  • At this time, System.Text.Json does not have support for a mechanism for handling circular references ( https://github.com/dotnet/runtime/issues/30820 ) because in reality, it's a bandaid over the fact your object is not readily serializable.

More info : https://dotnetcoretutorials.com/2020/03/15/fixing-json-self-referencing-loop-exceptions/

You have encountered a couple problems here.

Firstly , you need to increase MaxDepth from 5 to 6 :

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

Demo fiddle #1 here .

The JSON you are trying to serialize looks like this:

{                                               // 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"
}

It seems as though the primitive property values in the PrecedenceConstraints objects count as an extra level. If I comment out its properties I can serialize your data model at MaxDepth = 5 :

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

Demo fiddle #2 here demonstrating this. (The documentation doesn't explain the precise meaning of MaxDepth .)

Secondly , your PrecedenceConstraint lacks a public, parameterless constructor. As explained in the documentation How to migrate from Newtonsoft.Json to System.Text.Json : Deserialize to immutable classes and structs , deserialization of such types is not supported out of the box:

System.Text.Json supports only public parameterless constructors. As a workaround, you can call a constructor with parameters in a custom converter.

This prevents your data model from being deserialize successfully. One fix is to add a parameterless constructor as required by the documentation:

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.

Now your data model can be round-tripped at MaxDepth = 6 . Demo fiddle #3 here .

Check to see whether you have awaited all your asynchronous calls.

The only time I have encountered this error was when I forgot to add await to a function call and returned the not yet executed Task from my API endpoint.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM