2

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

4
  • 2
    Can you show the json and the Package class? Commented Mar 26, 2020 at 1:07
  • 2
    Please also show the Step class and the Constraint class. Commented Apr 3, 2020 at 16:40
  • Can't reproduce with the classes shown, see dotnetfiddle.net/XMuFq0. We need to see a minimal reproducible example to help you, which would include definitions for the Steps<T>, Step and Constraint types that demonstrate the problem. See: How to Ask. Commented Apr 3, 2020 at 23:14
  • 1
    @dbc here is the fiddle dotnetfiddle.net/c0dRLs Commented Apr 14, 2020 at 19:23

3 Answers 3

2

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. e.g. 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/

Sign up to request clarification or add additional context in comments.

2 Comments

Thanks for all the suggestions. The original implementation did have a circular object reference from Step->Constraint->Step as several pointed out but when I first got the error above I changed the last step to use name and then fix up the object reference under the covers. So its Step->Constraint->StepName.
You might want to update your answer now that the issue you referenced has been implemented.
0

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.

1 Comment

thanks this worked perfectly and I got to learn how to use fiddle as part of this!
0

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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.