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
TOML serializer silently loses Guid, decimal, TimeSpan and Uri properties, and throws on char instead of returning false #165
TomlSerializationProvider hands the model to Tomlyn 0.19.0 with only IgnoreMissingProperties = true:
the options: Essentials.SerializationProviders.Toml/TomlSerializationProvider.cs:29
the write: Toml.FromModel(obj, ModelOptions) at :58
There are no ConvertToToml / ConvertToModel hooks. Tomlyn's model writer does not treat several common .NET scalars as TOML values:
1. Guid, decimal, TimeSpan and Uri come back zeroed, and no error is raised. A model { Name="n", Id=<guid>, Price=12.34m, Timeout=30s, When=<DateTimeOffset>, Kind=K.B, U=new Uri("https://x.y/") } serializes to:
TrySerialize returns true. Reading it back gives Id = Guid.Empty, Price = 0, Timeout = 00:00:00, U = null.
The provider's own IsScalar (:219-228) lists Guid, decimal and TimeSpan as scalars, so it assumes Tomlyn writes them as values. It does not.
2. A char property makes TrySerialize throw. Tomlyn throws InvalidOperationException: Invalid primitive System.Char from ModelToTomlTransform.WritePrimitive. The catch list at :62-73 only covers TomlException, IOException and ObjectDisposedException. So:
TrySerialize throws instead of returning false, which breaks the contract in Essentials/ISerializationProvider.cs:29-35.
TrySerializeAsync faults.
b283163 fixed the same gap for System.Text.Json ("Report System.Text.Json's InvalidOperationException as a failed serialize").
Why it matters
Any persistence provider configured with TOML (FileSystem, Temp, ConfigHome, DataHome over Shared/FileRecordStore.cs) does this:
It stores a record with a Guid id or a decimal amount and reports success.
RetrieveAsync later returns a non-null object with the fields reset to their defaults.
Because the object is non-null, the null guard does not fire.
The data is lost silently. Switching the serializer from JSON to TOML changes behaviour with no error.
The round-trip model in Essentials.Tests/SerializationRoundTripTests.cs:74-89 only has a string, an int, a list and a nested object, so nothing catches this.
This was reproduced against Tomlyn 0.19.0 with the provider's exact options at HEAD 7e2b3e2.
Suggested fix
Add ConvertToToml / ConvertToModel to ModelOptions so that:
Guid, TimeSpan, Uri and char are written as strings and parsed back;
decimal is written as a string, or as a TOML float when lossless, and parsed back.
Add catch (InvalidOperationException) to TrySerialize, and consider TargetInvocationException for throwing getters, so any type Tomlyn still rejects reports false rather than throwing.
Acceptance criteria
A shared round-trip test over a model with Guid, decimal, TimeSpan, Uri, char, DateTimeOffset and an enum gives equal values for every serialization provider, TOML included.
TrySerialize / TrySerializeAsync never throw for a type Tomlyn cannot represent. They return false.
Priority: High. Data is lost silently. Guid, decimal, TimeSpan and Uri properties come back as defaults while TrySerialize reports success, and every persistence provider configured with TOML inherits this. Separately, the char case breaks the ISerializationProvider contract (it throws instead of returning false).
Area:Essentials.SerializationProviders.Toml (TomlSerializationProvider), plus the shared round-trip tests in Essentials.Tests
Activity
matt-edmondson commentedon Oct 29, 2026
Triage
Guid,decimal,TimeSpanandUriproperties come back as defaults whileTrySerializereports success, and every persistence provider configured with TOML inherits this. Separately, thecharcase breaks theISerializationProvidercontract (it throws instead of returningfalse).Essentials.SerializationProviders.Toml(TomlSerializationProvider), plus the shared round-trip tests inEssentials.TestsInvalidOperationExceptiongap for System.Text.Json.TrySerialize, not TOML.Generated by Claude Code