Skip to content

Develop/aot il2026#247

Open
IchiSamaFR wants to merge 9 commits into
maximn:masterfrom
IchiSamaFR:develop/AOT_IL2026
Open

Develop/aot il2026#247
IchiSamaFR wants to merge 9 commits into
maximn:masterfrom
IchiSamaFR:develop/AOT_IL2026

Conversation

@IchiSamaFR

Copy link
Copy Markdown

Summary

Migrate JSON deserialization to a source-generated JsonSerializerContext to enable AOT/trim compatibility on net8.0 and fix IL2026/IL3050 warnings. The reflection-based JsonSerializerConfiguration has been removed and replaced with a single GoogleMapsJsonSerializerContext used across all target frameworks.

A further improvement would be to introduce a T4 template (.tt file) that scans the project at code-generation time, discovers every enum type in the GoogleMapsApi assembly, and automatically regenerates the Converters dictionary inside EnumMemberJsonConverterFactory. This would guarantee that no enum converter is ever accidentally omitted when a new enum is added, removing the current need for manual registration and eliminating the associated runtime NotSupportedException risk on AOT targets.

Related issue

Fixes #195 — AOT/trim incompatibility with reflection-based JsonSerializer calls.

Changes

  • Added GoogleMapsJsonSerializerContext — a [JsonSerializable]-annotated partial context registering all response types, with [JsonSourceGenerationOptions] declaring custom converters (EnumMemberJsonConverterFactory, PriceLevelJsonConverter, OverviewPolylineJsonConverter, DurationJsonConverter<T>).
  • Removed JsonSerializerConfiguration (reflection-based options builder) and replaced with context-driven deserialization in MapsAPIGenericEngine.
  • Added [JsonConverter] attributes directly on entity properties (Leg, Step, Route, TransitDetails, Element, Geometry) so the source generator picks up the custom converters at compile time.
  • Extracted EnumMemberJsonConverterFactory to its own file; under NET5_0_OR_GREATER it uses a static whitelist of known enum types instead of MakeGenericType to stay AOT-safe.
  • Added [DynamicallyAccessedMembers] annotation to DurationJsonConverter<T> and EnumMemberJsonConverter<TEnum> to suppress IL2026/IL3050 on supported targets.
  • Replaced per-test JsonSerializerOptions _options setup with a shared JsonMultitargets utility that selects between JsonTypeInfo<T> and options-based deserialization for older targets.
  • Fixed SYSLIB1031 warnings by giving each [JsonSerializable] entry a unique TypeInfoPropertyName for ambiguous short names (Status, Result, Geometry, etc.).

Test plan

  • All existing unit tests (JsonConverterTests, EnhancedJsonConverterTests, EdgeCaseAndErrorHandlingTests, NullableReferenceTypesCompatibilityTests) updated to use JsonMultitargets and still pass.
  • New class test AotCompatibleTests added to ensure all enums are declared into the EnumMemberJsonConverterFactory.
  • Multi-framework build confirmed clean for net8.0 (AOT analyzers enabled), net6.0, netstandard2.0, net481, net462.
  • IL2026 / IL3050 / SYSLIB1031 warnings verified as resolved on the net8.0 target.

Checklist

  • dotnet format has been run.
  • dotnet test passes locally (with a valid GOOGLE_API_KEY for integration tests).
  • Multi-framework build passes (net8.0, net6.0, netstandard2.0, net481, net462).
  • XML documentation updated if public API surface changed.

maximn and others added 8 commits May 23, 2026 17:26
Enables IsAotCompatible on net8.0 and promotes IL2026/IL2104/IL3050/IL3053
to errors. Build fails today on JsonSerializer.Deserialize and the enum
JsonConverterFactory call sites — the exact surface a source-generated
JsonSerializerContext fix needs to cover.

Intentionally not for merge: this branch is a living acceptance test for
the eventual AOT fix.
@maximn

maximn commented May 27, 2026

Copy link
Copy Markdown
Owner

Hi, thanks for your contribution!

I'm planning to drop older TF soon (#242), so I'm thinking to delay your PR till this is done so it will be simpler and we won't need the precompile rules (#if NET5_0_OR_GREATER)

@maximn

maximn commented May 27, 2026

Copy link
Copy Markdown
Owner

You have many files with just spaces/tabs diff (for example GoogleMapsApi/QueryStringParametersList.cs), can you remove them from the PR?

@IchiSamaFR

Copy link
Copy Markdown
Author

Hi,
I just removed the unintended whitespaces changes

@IchiSamaFR

Copy link
Copy Markdown
Author

Hi,

I just realized that maintaining this will be complicated, because after each new class or enum addition, it will be necessary to add the type to both GoogleMapsJsonSerializerContext and EnumMemberJsonConverterFactory.

That's why I created two T4 template generators: AOT_T4_Generator

@maximn

maximn commented May 31, 2026

Copy link
Copy Markdown
Owner

I've released v2. We can have a look now. Would it be easier for you to resolve conflict or redo it on master branch?

@IchiSamaFR

IchiSamaFR commented Jun 1, 2026

Copy link
Copy Markdown
Author

Yes i can, no problem i will redo it.
If it's okay, i would like to add the T4 Generator which will facilitate future integrations

@maximn

maximn commented Jun 1, 2026

Copy link
Copy Markdown
Owner

Thanks — happy to take the AOT work on master.

One ask on direction before you redo it: I'd rather not bring in the T4 generators. The maintenance burden they solve is real, but I think we can design it away instead of code-generating around it:

  1. Enum converters — register them directly on each enum declaration instead of a central whitelist:

    [JsonConverter(typeof(EnumMemberJsonConverter<TravelMode>))]
    public enum TravelMode { ... }

    The source generator picks these up at compile time, so we drop EnumMemberJsonConverterFactory + MakeGenericType (which is what actually triggers IL2026/IL3050), and there's no central list anyone can forget. That removes the need for T4 template API doesn't work on C# MVC3 project #2 entirely, and the AotCompatibleTests guard becomes optional.

  2. [JsonSerializable] root types — let's keep this hand-written in GoogleMapsJsonSerializerContext. It's a small, self-documenting list and it's exactly what every System.Text.Json AOT setup does. That removes T4 template Added a license and .gitignore entries #1.

Net result: no build-time tooling, nothing generated/checked-in to go stale, and both registries the templates exist to maintain are gone. If we ever genuinely outgrow the manual [JsonSerializable] list, I'd reach for a Roslyn incremental source generator rather than T4.

Also note the TFMs are now netstandard2.0;net8.0;net10.0 after v2, so most of the #if NET5_0_OR_GREATER branching should go away — only netstandard2.0 still needs the options-based path.

Sound good?

@IchiSamaFR

Copy link
Copy Markdown
Author

Okay, sounds good to me.
I’ll take a look and rework the AOT PR in that direction.

@IchiSamaFR

Copy link
Copy Markdown
Author

I tried to use JsonSerializable but it doesn't seems to go welll.

You can take a look at this branch : develop/AOT_without_T4

It is not possible to apply JsonSerializable directly on entity classes. The JsonSerializable attributes must be placed on a class that derives from JsonSerializerContext (for example GoogleMapsJsonSerializerContext) so the System.Text.Json source generator can produce the required JsonTypeInfo objects. Applying the attribute to entities will not trigger the generator.

@maximn

maximn commented Jun 8, 2026

Copy link
Copy Markdown
Owner

Thanks for trying it out — I think there's a mix-up between two different attributes here, and it's my fault for putting both points in one comment. Let me untangle them:

[JsonSerializable] — you're 100% right: this only works on a JsonSerializerContext-derived class, never on entities. I'm not asking to change that. Keep the hand-written list in GoogleMapsJsonSerializerContext exactly as you have it. That was point #2, and it stays as-is.

[JsonConverter] — this is a different attribute, and this is the one I meant for the enums. It can go directly on an enum declaration, and the source generator picks it up at compile time. Crucially, it references a closed generic type, so there's no MakeGenericType — which is the actual thing throwing IL2026/IL3050 today (EnumMemberJsonConverterFactory.CreateConverter).

So the change is:

// On each enum:
[JsonConverter(typeof(EnumMemberJsonConverter<TravelMode>))]
public enum TravelMode
{
    [EnumMember(Value = "driving")]
    Driving,
    // ...
}
// And delete EnumMemberJsonConverterFactory entirely — no factory, no MakeGenericType,
// no central whitelist to keep in sync. That removes both T4 templates' reason to exist
// and makes AotCompatibleTests optional.

The [JsonConverter] on the type itself isn't blocked by source-gen the way [JsonSerializable] on entities is — they're unrelated mechanisms. If you put it on each enum and drop the factory, the generator should emit clean code with no IL2026/IL3050.

On netstandard2.0: this is the nice part — the [JsonConverter] attribute on the enum is honored by the reflection-based serializer too, not just source-gen. So the enum approach works on both paths and you can drop the factory on every target, not just the modern ones. netstandard2.0 keeps the existing options-based (JsonSerializerOptions) deserialization entry point — it just no longer needs the factory registered, because the attribute resolves the converter at runtime. The only remaining #if should be choosing the source-gen JsonTypeInfo<T> context on net8.0/net10.0 vs. the options-based call on netstandard2.0; all the #if NET5_0_OR_GREATER branching around the enum converters can go. (Same trick applies to the non-enum converters — OverviewPolyline/Duration<T> can move to [JsonConverter] on their properties so they're attribute-driven on both paths.)

Give that a shot and let me know if the analyzers still complain — happy to look at a draft branch.

@maximn

maximn commented Jun 12, 2026

Copy link
Copy Markdown
Owner

Hi @IchiSamaFR — no rush at all, just checking in on this one. I think we'd untangled the last sticking point: the enum fix is [JsonConverter(typeof(EnumMemberJsonConverter<T>))] placed directly on each enum type (a closed generic, so no MakeGenericType and no IL2026/IL3050), and we drop EnumMemberJsonConverterFactory entirely. The [JsonSerializable] root list stays hand-written in GoogleMapsJsonSerializerContext exactly as you had it — that part was never the problem.

That same [JsonConverter]-on-the-type trick is honored by the reflection-based serializer too, so it works on netstandard2.0 as well and the only remaining #if is picking the source-gen JsonTypeInfo<T> path on net8/net10 vs. the options-based call on netstandard2.0.

Are you still up for reworking it in that direction on master? Totally fine either way — if you're busy I'm happy to pick up a draft from here and credit you, just let me know. Thanks again for digging into this!

@IchiSamaFR

Copy link
Copy Markdown
Author

Hello,
Sorry i made some tests but nothing works well as intended except by declaring each JsonEnumConverter into the GoogleMapsJsonSerializerContext.
Here is a test i made 2 weeks ago : develop/AOT_without_T4

Unfortunatly I had a priority project, i will not be able to work on it for 2 weekss

Except using T4 Generator, which is pretty annoying to generate each time, there is a new possibility, Source Generators

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

AOT build warning: IL2026 triggered by JsonSerializer.Deserialize usage

2 participants