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
- 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.
- 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.
- 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?
Design Native AOT-compatible web-service JSON deserialization
Problem
Enabling the trimming and Native AOT analyzers for
MaxMind.GeoIP2currentlyreports
IL2026andIL3050at the two reflection-basedJsonSerializer.Deserialize<T>calls inWebServiceClient.Replacing those calls with a source-generated
JsonSerializerContextappearsstraightforward, but a prototype is not behavior-preserving with the current
public response models. The models are records with
initproperties, andseveral properties use non-null default initializers. For JSON where a member is
missing, generated construction can pass
nullfor that member and overwritethe 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
WebServiceClientas requiring dynamic code/unreferenced code isalso awkward. Database and web-service implementations share
IGeoIP2Provider; applying the attributes consistently to that interface wouldmake 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
IsAotCompatibleafter both database andweb-service paths are analyzer-clean and covered by native-publish tests.
Possible directions
context, then map them to the public immutable response records. The wire
DTOs can encode missing-member defaults explicitly.
reflection-based web-service compatibility path can be annotated without
contaminating the database API contract.
distinguish missing JSON members from explicit
null, if this can preserveall 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
nullwhere defaultsare non-null.
IsAotCompatible=trueandVerifyReferenceAotCompatibility=true.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?