Origin
Ultrareview dime cycle 1 on #249 (fix/json-serializer-options-per-call-leak). Recorded as TRACKED per CONVENTIONS §1.0d-trigies-septies — belongs in its own campaign PR (touches every serialised type in both JSON assemblies), split out of #249's atomic scope.
Problem
#249 freezes the JsonSerializerOptions singletons so their reflection metadata cache is built once. That eliminates the per-call LCG-DynamicMethod leak observed on DIME production hosts. But the underlying serializer still uses runtime reflection to introspect every reachable type in the MTConnect graph on first serialization of that type — 10 Streams.Json classes in MTConnect.NET-JSON, 25 in MTConnect.NET-JSON-cppagent, plus the Devices/Header/Assets/Errors trees.
That reflection cost:
- Pays a large first-request latency the first time each response type is serialised.
- Blocks AOT scenarios (ReadyToRun, NativeAOT, iOS/tvOS mono, .NET 10 AOT publish) — the trimmer + AOT compiler cannot see through the runtime reflection, so trimmed AOT builds silently drop the metadata and fail to serialise at runtime.
- Emits
IL2026 / IL3050 trimming warnings under PublishTrimmed=true, currently muted because the whole assembly isn't AOT-audited.
Proposal
Introduce System.Text.Json source generation across both assemblies:
- Add a
[JsonSerializable(typeof(...))]-marked JsonSerializerContext per assembly: MTConnectJsonContext in MTConnect.NET-JSON, MTConnectCppagentJsonContext in MTConnect.NET-JSON-cppagent.
- Enumerate every serialised type on the hot path (grep every
JsonSerializer.Serialize / Deserialize call, walk from the root Response/Streams/Devices/Assets/Errors types, close the transitive closure).
- Wire the generated context into
JsonFunctions.DefaultOptions / IndentOptions via TypeInfoResolver on both singletons.
- Set
TrimmerRootAssembly / PublishTrimmed=true on a representative agent host and confirm the trimmer + AOT publish walk succeeds with zero IL2026 / IL3050 warnings on the JSON path.
- RED-first: pin every root Response type has a
JsonTypeInfo<T> on the context; pin trimmed publish output produces byte-identical JSON to the reflection path.
Non-goals
- Not in scope: source-gen for the XML surrogate assemblies (separate campaign, different serializer stack).
- Not in scope: switching the default resolver at runtime — the source-gen context should be additive (chained onto
DefaultJsonTypeInfoResolver), not replacing.
Acceptance
- Both assemblies expose a
JsonSerializerContext covering every serialised type in the reachable graph.
- Reflection-path baseline stays byte-identical (regression pins).
PublishTrimmed=true on a representative host publishes cleanly with zero IL2026 / IL3050 warnings from the JSON assemblies.
- Full unit + E2E suites green on bluefin.
Origin
Ultrareview dime cycle 1 on #249 (fix/json-serializer-options-per-call-leak). Recorded as TRACKED per CONVENTIONS §1.0d-trigies-septies — belongs in its own campaign PR (touches every serialised type in both JSON assemblies), split out of #249's atomic scope.
Problem
#249freezes theJsonSerializerOptionssingletons so their reflection metadata cache is built once. That eliminates the per-call LCG-DynamicMethod leak observed on DIME production hosts. But the underlying serializer still uses runtime reflection to introspect every reachable type in the MTConnect graph on first serialization of that type — 10 Streams.Json classes inMTConnect.NET-JSON, 25 inMTConnect.NET-JSON-cppagent, plus the Devices/Header/Assets/Errors trees.That reflection cost:
IL2026/IL3050trimming warnings underPublishTrimmed=true, currently muted because the whole assembly isn't AOT-audited.Proposal
Introduce
System.Text.Jsonsource generation across both assemblies:[JsonSerializable(typeof(...))]-markedJsonSerializerContextper assembly:MTConnectJsonContextinMTConnect.NET-JSON,MTConnectCppagentJsonContextinMTConnect.NET-JSON-cppagent.JsonSerializer.Serialize/Deserializecall, walk from the root Response/Streams/Devices/Assets/Errors types, close the transitive closure).JsonFunctions.DefaultOptions/IndentOptionsviaTypeInfoResolveron both singletons.TrimmerRootAssembly/PublishTrimmed=trueon a representative agent host and confirm the trimmer + AOT publish walk succeeds with zeroIL2026/IL3050warnings on the JSON path.JsonTypeInfo<T>on the context; pin trimmed publish output produces byte-identical JSON to the reflection path.Non-goals
DefaultJsonTypeInfoResolver), not replacing.Acceptance
JsonSerializerContextcovering every serialised type in the reachable graph.PublishTrimmed=trueon a representative host publishes cleanly with zeroIL2026/IL3050warnings from the JSON assemblies.