问题
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
回答1:
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.
回答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/
来源:https://stackoverflow.com/questions/60859589/system-text-json-jsonexception-a-possible-object-cycle-was-detected-which-is-n