Skip to content

AOT: route Body Converter JSON through JsonTypeInfo instead of the annotated JsonSerializer overloads #509

Closed

Description

@Greven145

Goal

Replace the annotated JsonSerializerOptions-based JsonSerializer overloads in the Body Converters with options.GetTypeInfo(Type) plus the JsonTypeInfo overloads, closing those sites as AOT blockers.

The insight this issue exists to carry

#430 originally concluded that these sites are permanently unfixable because the serialized type is arbitrary at runtime. That reasoning is wrong:

  • JsonSerializerOptions.GetTypeInfo(Type) is unannotated.
  • JsonSerializer.Serialize(object, JsonTypeInfo), Serialize(Utf8JsonWriter, object, JsonTypeInfo) and Deserialize(string, JsonTypeInfo<T>) are unannotated.
  • A runtime-determined type does not block this. In reflection mode the resolution path is the same, so output is identical. Under AOT a type missing from the context throws NotSupportedException — the existing open-world caveat, which must be documented wherever the source-generated context is introduced.

Current state on master (verified at 7408c31)

None of this has been implemented here. The comment attributing the work to a merged PR refers to a fork; the change is not in this repository's history.

  • src/Chatter.MessageBrokers/src/Chatter.MessageBrokers/JsonBodyConverter.cs — four annotated calls: JsonSerializer.Deserialize<TBody>(body.AsSpan(), ChatterJson.Options) (line 17), Deserialize<TBody>(Stringify(body), ChatterJson.Options) (line 18), Serialize<object>(null, ChatterJson.Options) (line 29) and Serialize(body, body.GetType(), ChatterJson.Options) (line 30).
  • src/Chatter.MessageBrokers/src/Chatter.MessageBrokers/MaterializingObjectConverter.cs:51 — JsonSerializer.Serialize(writer, value, runtimeType, options), deliberately dispatching on the runtime type to avoid converter re-entry.
  • src/Chatter.MessageBrokers.RabbitMQ/src/Chatter.MessageBrokers.RabbitMQ/RabbitMqBodyConverter.cs — calls the published Wire Serialization capability, ChatterJson.Deserialize<TBody> (line 10) and ChatterJson.Serialize(body) (line 20). It does not reference ChatterJson.Options and cannot: that field is internal (ChatterJson.cs:24) and Properties/AssemblyInfo.cs grants production InternalsVisibleTo only to Chatter.MessageBrokers.AzureServiceBus and Chatter.MessageBrokers.Reliability.Cosmos.
  • No file in the repository routes any serialization through options.GetTypeInfo.

Corrections to earlier notes

Design constraint

src/Chatter.MessageBrokers/CONTEXT.md defines Wire Serialization (ChatterJson) as a published capability whose System.Text.Json configuration is deliberately internal and appears in no public signature. It explicitly warns against reading that configuration as a caller-facing surface and against reaching for the internal options across an InternalsVisibleTo grant, stating that the sanctioned door is the published capability. Adding a public JsonSerializerOptions constructor parameter to a Body Converter therefore needs an explicit, recorded decision — it is a public-API and SemVer question and it runs against the current domain model. An alternative worth costing is extending the published capability with a metadata-aware entry point, keeping the options unpublished.

Scope

  • JsonBodyConverter (lines 17, 18, 29, 30) and MaterializingObjectConverter (line 51): route through GetTypeInfo plus the JsonTypeInfo overloads.
  • JsonBodyConverter's null-body path must keep returning the literal "null", byte-identical to today.
  • RabbitMqBodyConverter: decide how a sibling package reaches metadata-aware serialization without reaching for internals. Resolve this against the Wire Serialization term before writing code.
  • Byte-for-byte wire parity must be preserved. The parity contracts documented throughout ChatterJson.CreateOptions() are the constraint, and persisted outbox rows plus rolling deploys depend on them.
  • Add smoke tests: a runtime-typed body round trip, a null body, and an unregistered type surfacing the expected failure. This depends on the harness from AOT Phase 0: Infrastructure and red baseline #276.
  • Measure real publish-time and compile-time diagnostic counts before and after, from a clean tree.

Hazards

  • Resolving JsonSerializerOptions from DI by type lets a host's own singleton silently substitute for Chatter's.
  • RabbitMqBodyConverter is selected by the default MessageBodyType of "application/json; charset=utf-8", so it is on the default path for every RabbitMQ delivery, not an edge case.

Part of #275.

Activity

  1. Greven145 commented on Sep 21, 2026

    @Greven145
    Author

    Implemented in Greven145#19. Verified on a clean tree (CI SDK 10.0.401):

    • Publish-time warnings in samples/Chatter.Samples.RabbitMq.Aot: 10 lines to 0.
    • Trim baseline: 54 to 42 (12 entries closed, none added).
    • Chatter.MessageBrokers 712/712, RabbitMQ 284/284 (net8.0, net10.0). Native AOT smoke: 27 untagged pass, 4 KnownGap unchanged, 0 failed.
    • Real Native AOT round trip against RabbitMQ: exit 0.

    Not changed: the routing-slip 'permanent' remarks (tracked in #510). No public signature changed.

  2. changed the title [-]AOT: route converter JSON through options.GetTypeInfo, remove wrong 'permanent' claims[/-] [+]AOT: route Body Converter JSON through JsonTypeInfo instead of the annotated JsonSerializer overloads[/+] on Sep 23, 2026
  3. Greven145 commented on Oct 1, 2026

    @Greven145
    Author

    Done. PR #19.

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

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions