You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
JsonSerializationProvider.TrySerialize and Deserialize throw InvalidOperationException instead of returning false/default #161
JsonSerializationProvider is the default serializer. Its docs promise that TrySerialize returns "True if the serialization was successful, false otherwise" and that Deserialize<T> returns "default if deserialization fails". The methods only catch JsonException, NotSupportedException, and IOException/ObjectDisposedException (serialize) or ArgumentException (deserialize).
System.Text.Json reports two common type-shape errors as InvalidOperationException, and both escape these methods:
two members that map to the same JSON name
a deserialization constructor whose parameters do not bind to properties
The affected code is in Essentials.SerializationProviders.Json/JsonSerializationProvider.cs:
TrySerialize (L55-L84)
Deserialize<T> (L92-L115)
The YAML provider already treats this case as a failure (YamlSerializationProvider.cs L91, catch (InvalidOperationException) { return default; }). The Newtonsoft provider reports these errors as JsonSerializationException, which it catches. That makes the default JSON provider the only one that breaks its Try/Deserialize contract here.
Reproduction (verified on net10.0 against the project)
ISerializationProviderp=newJsonSerializationProvider();p.TrySerialize(newDup{A=1,B=2},newStringWriter());// throws InvalidOperationException: The JSON property name for 'Dup.x' collides with another property.p.Deserialize<Dup>("{\"x\":1}"u8);// throws the samep.Deserialize<BadCtor>("{\"Value\":1}"u8);// throws InvalidOperationException: Each parameter in the deserialization constructor on type 'BadCtor' must bind to an object property or field...publicclassDup{[JsonPropertyName("x")]publicintA{get;set;}[JsonPropertyName("x")]publicintB{get;set;}}publicclassBadCtor{[JsonConstructor]publicBadCtor(intother){}publicintValue{get;set;}}
The second case needs no attributes. A class whose only constructor takes a parameter not named after a property hits it, for example class C(string p) { public string Path { get; } = p; }.
Suggested fix / acceptance criteria
Add catch (InvalidOperationException) to TrySerialize returning false, and to Deserialize<T> returning default, matching the YAML provider.
Add tests in Essentials.Tests that cover both reproductions:
TrySerialize returns false.
Deserialize<T> returns default.
Serialize (the non-Try wrapper) still throws its own InvalidOperationException.
Priority: Medium. The default serializer breaks its documented Try/Deserialize contract for common type shapes, such as a primary constructor whose parameter isn't named after a property, so callers that rely on false/default crash instead. The fix itself is small.
Area / suggested assignee:Essentials.SerializationProviders.Json/JsonSerializationProvider.cs (TrySerialize, Deserialize<T>) and Essentials.Tests. Owner: @matt-edmondson
Next step: add catch (InvalidOperationException) to both methods, matching the YAML provider. Add the two reproductions as tests, and assert that Serialize still throws.
Activity
matt-edmondson commentedon Oct 20, 2026
Triage
false/defaultcrash instead. The fix itself is small.Essentials.SerializationProviders.Json/JsonSerializationProvider.cs(TrySerialize,Deserialize<T>) andEssentials.Tests. Owner: @matt-edmondsonTrySerialize; neither covers System.Text.Json. Per the issue, land it with or after RetrieveOrCreateAsync overwrites a stored file it cannot parse with new T(), destroying the user's data #82 (RetrieveOrCreateAsynctreatingdefaultas missing).catch (InvalidOperationException)to both methods, matching the YAML provider. Add the two reproductions as tests, and assert thatSerializestill throws.Generated by Claude Code