v6.1.0
What's new in v6.1
A JSON audit, and three defects it found. All three shared one shape: System.Text.Json fell back to reflection where the library expected a converter, and the result was silence rather than an exception. Nothing that previously worked changes — every fix replaces a crash or a wrong answer.
⚠️ Value set elements without a converter now serialize as their text form, not as an object. This is the one visible payload change. A validated wrapper —StringSet<PermissionKey>,GuidSet<TenantId>, … — whose element type carries no[JsonConverter]used to be handed to System.Text.Json's reflection path, which wrote[{"Value":"users.read"}], or[{}]for the generator-typical shape of a record struct over a private field. The[{}]form destroyed data on read; both disagreed with the{users.read}stored in PostgreSQL. Elements now go through the family's own text form —["users.read"]for string- and Guid-backed sets,[1,2]for integer-backed ones, identical to the primitive each wraps — and reads re-run the element'sIParsablevalidation. If you serialize such a set and have persisted or published the old object form, that payload shape changes. Registering a converter for the element type (on the options, the property, or the type) overrides this, exactly as before.- The same fix reaches the NodaTime sets, which had the identical failure —
[{"Calendar":{…},"Year":2024,…}]on write,defaulton read.AddRangeConverters()alone is now enough; the satellite additionally exposesAddNodaTimeRangeConverters()for bare NodaTime values sitting next to a set, which the element hook does not reach. Composes withConfigureForNodaTimein either registration order. - Nullable range properties no longer throw.
HandleNullrouted nulls into the write path, which dereferenced them: serializing an object with a nullInt32Range?threwNullReferenceException. It writesnull. Reads still reject a null token — use"empty". - Ranges reached through
objectno longer throw.Serialize<object>(range), anobject-typed property and heterogeneous collections all present the union's sealed variant, for which the converter could not be constructed — a reflectionArgumentExceptionescaped. Variants now serialize to the same literal, and reads into a variant-typed declaration reject a literal of the wrong shape.
New API: IValueSetFactory<TSet, T>.ElementJsonConverter (a defaulted virtual static; the interface is closed to external implementation), RangeVariantJsonConverter<TVariant, TRange, T>, and AddNodaTimeRangeConverters().