Skip to content

Design Native AOT-compatible web-service JSON deserialization #505

Description

@KonstantinKritsin

Design Native AOT-compatible web-service JSON deserialization

Problem

Enabling the trimming and Native AOT analyzers for MaxMind.GeoIP2 currently
reports IL2026 and IL3050 at the two reflection-based
JsonSerializer.Deserialize<T> calls in WebServiceClient.

Replacing those calls with a source-generated JsonSerializerContext appears
straightforward, but a prototype is not behavior-preserving with the current
public response models. The models are records with init properties, and
several properties use non-null default initializers. For JSON where a member is
missing, generated construction can pass null for that member and overwrite
the model's default initializer. In the current test suite, the direct
source-generation prototype caused nine web-service tests to fail, including
missing-key behavior and null-reference failures.

Annotating WebServiceClient as requiring dynamic code/unreferenced code is
also awkward. Database and web-service implementations share
IGeoIP2Provider; applying the attributes consistently to that interface would
make AOT-safe database methods appear unsafe to consumers, while applying them
only to the implementation produces analyzer contract warnings.

Goal

Make the web-service path statically analyzable while preserving the existing
public model API and JSON semantics, especially defaults for missing members.
The package should only declare IsAotCompatible after both database and
web-service paths are analyzer-clean and covered by native-publish tests.

Possible directions

  1. Deserialize into internal mutable wire DTOs using a source-generated
    context, then map them to the public immutable response records. The wire
    DTOs can encode missing-member defaults explicitly.
  2. Split or reshape the common provider abstraction so that any deliberately
    reflection-based web-service compatibility path can be annotated without
    contaminating the database API contract.
  3. Explore custom source-generated converters/type-info customization that can
    distinguish missing JSON members from explicit null, if this can preserve
    all existing behavior without duplicating the model graph.

The internal DTO approach is likely the least surprising public API, although
it adds a second model graph and mapping maintenance.

Suggested verification

  • Keep all existing web-service tests unchanged and passing.
  • Add explicit tests for missing members versus explicit null where defaults
    are non-null.
  • Build with IsAotCompatible=true and
    VerifyReferenceAotCompatibility=true.
  • Publish and execute a Native AOT smoke application that deserializes
    representative success and error responses.

The database-reader dependency has a related but separate model-mapping design
discussion in maxmind/MaxMind-DB-Reader-dotnet#321.

Would internal source-generated wire DTOs be an acceptable direction for a
follow-up implementation PR?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions