Skip to content

feat(stjm): source generator for reflection-free registration and generated discriminator members #211

Description

@egil

Follow-up to #207, #210 and #209. Do not start before #207 has landed and #210 has an explicit registration API; the generator emits into that API.

What a generator buys

Analysis result (see the linked issues for the measurements): a source generator does not improve the steady-state read path. Removing the discriminator probe and the second ReadStack would require generating a complete object deserializer (naming policies, case-insensitivity, ctor binding, required/init, extension data, number handling, callbacks, unmapped members), which STJ's own generator refuses to do for the same reason. That option is rejected.

What it does buy:

  1. Discovery without reflection. Emit the AddMigratable<T>() / AddStaticMigration<,>() / AddExternalMigration<,,>() calls from docs(stjm): document trimming/NativeAOT limits, annotate reflection entry points, then add trim-safe explicit registration #210 for every [JsonMigratable] type and IMigrate<,> implementation in the compilation, replacing RegisterMigratorsFromAssembly. Cross-assembly types come through the referenced assemblies' own generated registrations (chainable), not by scanning references.

  2. Discriminator as a real member. For partial [JsonMigratable] types, emit the discriminator property so the type's JsonTypeInfo is never modified and STJ's fast-path serialization applies to the migratable type itself (after perf(stjm): register migration through the resolver chain so source-gen fast-path serialization survives #207 only nested and unrelated types regain it):

    partial record UserV2
    {
        [JsonPropertyName("$type"), JsonPropertyOrder(int.MinValue)]
        public string JsonMigrationDiscriminator => "user-v2";
    }

    JsonMigratableConverterFactory.AddDiscriminatorProperty already early-returns when the property exists. Only attribute-declared values can be baked in; SetTypeDiscriminatorPropertyName and GetTypeDiscriminatorFrom are runtime configuration and must either be rejected when a generated member exists or take precedence with a diagnostic. Document that users can hand-write the same member today.

Shape

  • Incremental generator (ForAttributeWithMetadataName), netstandard2.0, same project layout and Verify snapshot testing as Egil.StronglyTypedPrimitives; packed as an analyzer asset alongside the analyzer from feat(stjm): analyzer foundation and migration diagnostics #209 (same package id).
  • Opt-in entry point mirroring STJ: [JsonMigrationContext] partial class AppMigrations producing AppMigrations.Default with a Configure(JsonMigrationBuilder) method; builder.AddContext(AppMigrations.Default).
  • .NET 11 unions: emit nothing special; routing stays runtime (UnionCaseRouting reads cases from JsonTypeClassifierContext).
  • Benchmarks: add a source-gen-registered variant of the existing scenarios and confirm the migratable root type serializes on the fast path with the generated member.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requeststjmEgil.SystemTextJson.Migration scope

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions