Extensions: Native AOT JsonSerializerContext + formatting extension methods - #440
Merged
Merged
Conversation
…sion methods PhoneNumberJsonContext + PhoneNumberJsonOptions ship a source-generated JsonSerializerContext for PhoneNumber plus ready-made JsonSerializerOptions, so consumers building trimmed/Native AOT apps don't have to hand-write one. The two pieces have to be combined carefully: PhoneNumber.DefaultInstanceForType is a public get-only property returning the instance itself, so the raw member-based JsonTypeInfo<PhoneNumber> the source generator produces recurses into itself without end if ever invoked directly (JsonSerializer.Serialize(v, Context.Default.PhoneNumber) throws once the writer hits its max depth, or overflows the stack at a larger depth limit) instead of using PhoneNumberConverter. PhoneNumberJsonOptions.Default/Create wire the context and the converter onto the same JsonSerializerOptions so callers can't get this wrong; PhoneNumberJsonOptions.Serialize/Deserialize additionally avoid the IL2026/IL3050 trim/AOT warnings a consumer's own analyzer would otherwise raise on the options-based JsonSerializer overloads. Verified with a throwaway `dotnet publish -p:PublishAot=true` app (not part of this repo). IsAotCompatible is now set for Extensions' modern TFMs too, matching PhoneNumbers.csproj, so this stays enforced in CI. PhoneNumberExtensions adds ToE164/ToNationalFormat/ToInternationalFormat/ IsValid as extension methods on PhoneNumber, for the common formatting one-liners callers currently have to route through PhoneNumberUtil.GetInstance() by hand. Note: PhoneNumberAttribute (a ValidationAttribute backed by PhoneNumberUtil.IsValidNumber) already exists on main via PR #405 and needed no changes here. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WVGRJyQwtvBGXAmWswxyqW
Codecov Report❌ Patch coverage is
❌ Your patch check has failed because the patch coverage (85.71%) is below the target coverage (90.00%). You can increase the patch coverage or adjust the target coverage. Additional details and impacted files@@ Coverage Diff @@
## main #440 +/- ##
==========================================
+ Coverage 87.26% 87.28% +0.02%
==========================================
Files 41 43 +2
Lines 3831 3845 +14
Branches 978 980 +2
==========================================
+ Hits 3343 3356 +13
- Misses 284 285 +1
Partials 204 204 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
…umberJsonOptions.Create Create() previously replaced baseOptions.TypeInfoResolver outright with PhoneNumberJsonContext.Default, which broke the exact "combine with your own JsonSerializerContext for a DTO with a PhoneNumber property" workflow the method's own doc comment described: a consumer's own context alone has no metadata for PhoneNumber, and PhoneNumberJsonContext.Default alone has none for their DTO, so passing either as baseOptions threw NotSupportedException. Combine the two resolvers instead when baseOptions already carries one. Also corrects a doc comment that said PhoneNumber.DefaultInstanceForType "returns the instance itself" (it returns the static PhoneNumber.DefaultInstance, not `this`) and drops a dead net6-only #else branch in a test that only ever targets net8.0/net10.0. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WVGRJyQwtvBGXAmWswxyqW
This was referenced Sep 10, 2026
Open
Closed
Open
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Changes
PhoneNumberJsonContext(a source-generatedJsonSerializerContext) andPhoneNumberJsonOptions(ready-madeJsonSerializerOptions, plusSerialize/Deserializehelpers) toPhoneNumbers.Extensions, so consumers building trimmed/Native AOT apps get JSON support forPhoneNumberwithout hand-writing a context.PhoneNumberExtensionswithToE164()/ToNationalFormat()/ToInternationalFormat()/IsValid()extension methods onPhoneNumber, for the one-liners callers currently have to route throughPhoneNumberUtil.GetInstance()by hand.IsAotCompatibleonPhoneNumbers.Extensions's modern TFMs (net8.0/net10.0), matchingPhoneNumbers.csproj, so the trim/AOT analyzers keep this enforced in CI going forward.PhoneNumbers.Extensions.Test.On PR #405 /
PhoneNumberAttributePer the task brief, I audited whether a
ValidationAttribute/IValidatableObjectalready existed before adding anything. It does —PhoneNumberAttribute : ValidationAttribute(backed byPhoneNumberUtil.IsValidNumber, with an optionalRegionfor national-format strings) was added by PR #405 (commitse559f548/257b975e, merged as9466f2b0) and is present and unchanged onmaintoday, with its own tests inTestPhoneNumberAttributeValidation.cs. Nothing was reverted; no changes were needed here. This PR does not touch it.The JSON gotcha (found empirically, not just theorized)
PhoneNumber.DefaultInstanceForTypeis a public get-only property that returns the instance itself. That means the raw member-basedJsonTypeInfo<PhoneNumber>the source generator produces forPhoneNumberJsonContext— if you ever callJsonSerializer.Serialize(number, PhoneNumberJsonContext.Default.PhoneNumber)directly instead of going through options — walks straight into infinite recursion instead of ever usingPhoneNumberConverter(reproduced locally:InvalidOperationExceptionat the writer's max depth, or a real stack overflow at a higher depth limit).PhoneNumberJsonOptions.Default/CreatewirePhoneNumberJsonContext(TypeInfoResolver) andPhoneNumberConverter(Converters) onto the same options instance so this can't happen by accident, and the doc comments onPhoneNumberJsonContextspell out why.Separately, the options-based
JsonSerializer.Serialize(value, options)/Deserialize(...)overloads are always flagged IL2026/IL3050 by a consumer's own trim/AOT analyzer, since the analyzer can't statically proveoptions.TypeInfoResolvernever falls back to reflection.PhoneNumberJsonOptions.Serialize/DeserializecarryUnconditionalSuppressMessageinternally (the library can make that guarantee; the analyzer can't) so consumers get a genuinely warning-free path.Verified end-to-end with a throwaway
dotnet publish -r linux-x64 --self-contained -p:PublishAot=trueconsole app (not part of this repo) referencing the builtPhoneNumbers/PhoneNumbers.Extensionsprojects: the published native binary serializes and round-trips aPhoneNumbercorrectly throughPhoneNumberJsonOptions, and confirms the raw-context bypass throws rather than hangs/crashes.Testing
dotnet build csharp --no-restore— full solution, all TFMs — clean, 0 warnings.dotnet test csharp/PhoneNumbers.slnx -p:TargetFrameworks=net10.0— 464 passed (414PhoneNumbers.Test+ 50PhoneNumbers.Extensions.Test), 0 failed.dotnet pack -c Release csharp/PhoneNumbers.Extensions -p:VersionPrefix=9.0.38(above the pinned package-validation baseline) — packs clean, no API-compat breaks; purely additive surface.