Skip to content

v6.1.0

Choose a tag to compare

@CaffeinatedCoder CaffeinatedCoder released this 14 Aug 22:54
· 100 commits to main since this release

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's IParsable validation. 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, default on read. AddRangeConverters() alone is now enough; the satellite additionally exposes AddNodaTimeRangeConverters() for bare NodaTime values sitting next to a set, which the element hook does not reach. Composes with ConfigureForNodaTime in either registration order.
  • Nullable range properties no longer throw. HandleNull routed nulls into the write path, which dereferenced them: serializing an object with a null Int32Range? threw NullReferenceException. It writes null. Reads still reject a null token — use "empty".
  • Ranges reached through object no longer throw. Serialize<object>(range), an object-typed property and heterogeneous collections all present the union's sealed variant, for which the converter could not be constructed — a reflection ArgumentException escaped. 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().