Reputation: 43
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
Upvotes: 2
Views: 9561
Reputation: 421
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.
Upvotes: 0
Reputation: 116786
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.
Upvotes: 0
Reputation: 12470
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 :
[JsonIgnore]
attribute on the navigation properties that go in reverse so that they aren't serialized. More info : https://dotnetcoretutorials.com/2020/03/15/fixing-json-self-referencing-loop-exceptions/
Upvotes: 2