Repository navigation
AOT: route Body Converter JSON through JsonTypeInfo instead of the annotated JsonSerializer overloads #509
Description
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)andDeserialize(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) andSerialize(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) andChatterJson.Serialize(body)(line 20). It does not referenceChatterJson.Optionsand cannot: that field isinternal(ChatterJson.cs:24) andProperties/AssemblyInfo.csgrants productionInternalsVisibleToonly toChatter.MessageBrokers.AzureServiceBusandChatter.MessageBrokers.Reliability.Cosmos.- No file in the repository routes any serialization through
options.GetTypeInfo.
Corrections to earlier notes
- The "Measured (scratch tree)" figures and the comment's verification numbers describe a fork tree and cannot be reproduced here. Re-measure from scratch.
.github/aot-trim-warnings-baseline.jsondoes not exist —.github/contains onlydependabot.yml,scripts/andworkflows/— so the "54 to 42" target is not meaningful until a baseline file is introduced. That file is part of AOT: CI gate for trim-analyzer annotation regressions #404's gate decision.- No "permanent" AOT
<remarks>exists in any of the six files named in the original scope. That scope item is a no-op here. - The sequencing note "do after Implement Outbox pattern #18" refers to a fork PR. The real ordering dependency is on AOT Phase 3: ChatterJson serialization dual-path #279 (which introduces the AOT serialization path and the source-generated context) and on AOT: ChatterJson's shared options field initializer eagerly builds the reflection-based resolver #431's isolation of the reflection-based default.
RabbitMqBodyConverterneeds a different treatment from the twoChatter.MessageBrokersconverters, because it sits in a separate package and consumes the published capability rather than the internal options.
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) andMaterializingObjectConverter(line 51): route throughGetTypeInfoplus theJsonTypeInfooverloads.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
JsonSerializerOptionsfrom DI by type lets a host's own singleton silently substitute for Chatter's. RabbitMqBodyConverteris selected by the defaultMessageBodyTypeof"application/json; charset=utf-8", so it is on the default path for every RabbitMQ delivery, not an edge case.
Part of #275.
Activity
Greven145 commentedon Sep 21, 2026
Implemented in Greven145#19. Verified on a clean tree (CI SDK 10.0.401):
samples/Chatter.Samples.RabbitMq.Aot: 10 lines to 0.Chatter.MessageBrokers712/712, RabbitMQ 284/284 (net8.0, net10.0). Native AOT smoke: 27 untagged pass, 4 KnownGap unchanged, 0 failed.Not changed: the routing-slip 'permanent' remarks (tracked in #510). No public signature changed.
[-]AOT: route converter JSON through options.GetTypeInfo, remove wrong 'permanent' claims[/-][+]AOT: route Body Converter JSON through JsonTypeInfo instead of the annotated JsonSerializer overloads[/+]AOT: route body converter JSON through GetTypeInfo (brenpike#509) (#19)
fix(messagebrokers): thread jsonOptions through the real RoutingSlip …
AOT: source-generate RoutingSlip serialization (brenpike#510) (#20)
Greven145 commentedon Oct 1, 2026
Done. PR #19.