Remove embedded Newtonsoft.Json, migrate to System.Text.Json exclusively#5959
Remove embedded Newtonsoft.Json, migrate to System.Text.Json exclusively#5959Robbie-Microsoft wants to merge 7 commits intomainfrom
Conversation
…vely - Delete the embedded Newtonsoft source tree (json/ folder, ~100 files) - Define SUPPORTS_SYSTEM_TEXT_JSON unconditionally for all TFMs - Remove HAVE_* Newtonsoft build-infra defines from csproj - Add System.Text.Json NuGet package for netstandard2.0, net462, net472 - Move Platforms/net STJ helpers to compile for all TFMs - Strip all #if SUPPORTS_SYSTEM_TEXT_JSON / #else branches from ~44 source files - Migrate Android broker files from Microsoft.Identity.Json to STJ - Migrate iOS IntuneEnrollmentIdHelper from Newtonsoft to STJ - Remove Newtonsoft.Json PackageReference from all test/lab projects - Migrate ~18 test/lab files from Newtonsoft APIs to STJ equivalents - Fix ArrayBufferWriter<T> (not available pre-.NET5) with MemoryStream Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
token_response, id_token_response, at_cache_value, id_token_cache_value, rt_cache_value, account_cache_value are JSON objects in the test data files, not primitive strings. STJ's GetValue<string>() only works on leaf nodes; use ToJsonString() to serialize them as strings (matching prior Newtonsoft JToken.ToString() behavior). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Very nice, great to see this @Robbie-Microsoft |
- ManagedIdentityTests.NetFwk.cs: remove #if NET8_0_OR_GREATER guard around JsonConvert.DeserializeObject; use System.Text.Json.JsonSerializer unconditionally (the NetFx project targets net48 where the #else branch compiled, but Microsoft.Identity.Json no longer exists) - AndroidBrokerHelper.cs: replace (string?) nullable cast with ?.GetValue<string>() — nullable annotation syntax requires #nullable enable context which the Android file does not have (CS8632) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
JsonSerializer.Deserialize<T>() without a JsonSerializerContext uses reflection which is incompatible with iOS Native AOT (IL3050). Add a local IntuneSerializerContext (partial JsonSerializerContext) in IntuneEnrollmentIdHelper.cs with [JsonSerializable(typeof(EnrollmentIDs))] and use the non-generic JsonSerializer.Deserialize(json, type, context) overload which is AOT-safe. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ClaimExtractor - LabResponseHelper.Deserialize<UserConfig/AppConfig>: add PropertyNameCaseInsensitive=true to match Newtonsoft's case-insensitive ToObject<T>() behavior. Without this, Key Vault JSON properties like 'LabName'/'labName' would not deserialize into UserConfig.LabName, causing all integration tests to fail with 'lab name is not set on user'. - JwtClaimExtractor.TryExtractExpirationClaim: STJ returns JsonElement (not long) when deserializing Dictionary<string,object>. Handle JsonElement.TryGetInt64() so expiry is correctly extracted from attestation JWTs. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Agree |
| #if SUPPORTS_SYSTEM_TEXT_JSON | ||
| using JObject = System.Text.Json.Nodes.JsonObject; | ||
| #else | ||
| using Microsoft.Identity.Json.Linq; |
There was a problem hiding this comment.
Do we have enough unit tests / or did we manual test to ensure we are still good in net462/472/netstandard2.0? an nothing is broken?
There was a problem hiding this comment.
net462/472/netstandard2.0 aren't targeted by any test project in this repo - that gap predates this PR. The good news is that STJ 6.0.10 itself targets netstandard2.0, so its behavior is consistent across all these TFMs. The net48 unit tests exercise all the same code paths (JsonHelper, cache serialization, IdToken parsing, etc.) and all pass. Happy to add a follow-up PR that adds net462/net472 to the unit test TargetFrameworks if you'd like explicit coverage.
There was a problem hiding this comment.
Happy to add a follow-up PR that adds net462/net472 to the unit test TargetFrameworks if you'd like explicit coverage.
I don't think we should do this, but we need a test pass on key features.
There was a problem hiding this comment.
Ran the unit tests locally targeting net462 (1,248 passed) and net472 (1,845 passed) by temporarily adding those TFMs — all passing, no JSON-related failures.
There was a problem hiding this comment.
I don't think this is what @bgavrilMS suggested to do for the validation.
| { | ||
| // Output buffer to store the merged JSON | ||
| var outputBuffer = new ArrayBufferWriter<byte>(); | ||
| using var outputStream = new System.IO.MemoryStream(); |
There was a problem hiding this comment.
any concerns with swiching from ArrayBufferWriter to MemoryStream?
There was a problem hiding this comment.
No concerns. MemoryStream.ToArray() has a small extra allocation vs ArrayBufferWriter, but Merge() isn't on any hot path. ArrayBufferWriter was only available from .NET 5+, so switching to MemoryStream was required to support net462/472/netstandard2.0.
There was a problem hiding this comment.
ArrayBufferWriter actually ships in the System.Memory package and is available down to netstandard2.0 / net462 / net472 (MSAL already pulls System.Memory in transitively)? so it's not a TFM constraint as I can see.
nit - prefer using var outputStream = new MemoryStream(); without the fully-qualified System.IO. since System.IO should already be in usings of that file.
There was a problem hiding this comment.
Tried it — ArrayBufferWriter is actually internal in the resolved System.Buffers 4.5.1 package for netstandard2.0/net462/net472, so it doesn't compile on those TFMs. The type only became public in later runtime versions. MemoryStream is the correct choice here for cross-TFM compatibility
|
Good PR overall - but we have no test coverage for the net462/472/netstandard2.0 builds of MSAL, so the swap from Newtonsoft to STJ on those TFMs (especially IdToken.Parse claim shapes and cache serialization) needs to be tested. |
Adds net462 and net472 as TargetFrameworks to verify System.Text.Json works correctly on older .NET Framework versions after the Newtonsoft migration. Files excluded per TFM due to API availability: - net462: CertificateRequest/ECCurve not available (added 4.7.1/4.7.2); ImdsV2 unsupported (#if NET462 guard in production code) - net472: WebView2 test (Desktop lib targets net462 only); GetCertHash(HashAlgorithmName) not available (added net48) Results: net462 1248 passed, net472 1845 passed, 0 failures. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This reverts commit ed9afdf.
ashok672
left a comment
There was a problem hiding this comment.
Looks good. I tried building locally for all supported TFMs and the build is clean without warning. Thanks for this PR.
|
Managed to validate the netstandard2.0 code path with actual test execution. Approach: Created a standalone net6.0 test project (outside the msal-dotnet repo) that references MSAL via ProjectReference. When the consumer targets net6.0, MSBuild selects MSAL's netstandard2.0 binary — exactly the TFM under question. Used MSTest 3.6.3 which ships a native net6.0 adapter. Tests written and results:
Tests exercise:
All three STJ deserialization paths work correctly on netstandard2.0. ✅ |
Summary
Removes the embedded Newtonsoft.Json source tree from MSAL.NET and migrates all JSON serialization to System.Text.Json (STJ).
Previously, MSAL.NET bundled a private copy of Newtonsoft.Json (under the
Microsoft.Identity.Jsonnamespace) compiled directly into the assembly for all target frameworks exceptnet8.0. This PR eliminates that dependency entirely.Changes
Deleted
src/client/Microsoft.Identity.Client/json/— the entire embedded Newtonsoft source tree (~100 files)Infrastructure
SUPPORTS_SYSTEM_TEXT_JSONcompile flag is now defined unconditionally for all TFMs (was previously onlynet8.0\netcore)HAVE_*Newtonsoft build-infraDefineConstantsfrom the main csprojSystem.Text.JsonNuGet package reference fornetstandard2.0,net462, andnet472(it is built intonet8.0and later)Platforms/net/STJ helper files (JsonObjectAttribute.cs,JsonStringConverter.cs,MsalJsonSerializerContext.cs) now compile for all TFMs instead of onlynet8.0Main library (~44 files)
#if SUPPORTS_SYSTEM_TEXT_JSON / #elseconditional blocks, keeping only the STJ code pathsArrayBufferWriter<T>(unavailable pre-.NET 5) withMemoryStreaminJsonHelper.csPlatform files
BrokerRequest,AndroidBrokerHelper,AndroidAccountManagerBroker,AndroidBrokerInteractiveResponseHelper,AndroidContentProviderBroker): migrated fromMicrosoft.Identity.Json.LinqtoSystem.Text.Json.NodesIntuneEnrollmentIdHelper: migrated fromJsonConvert.DeserializeObjecttoJsonSerializer.DeserializeTests / Lab
JObject,JToken,JsonConvert,[JsonProperty]) to STJ equivalents (JsonObject,JsonNode,JsonSerializer,[JsonPropertyName])Newtonsoft.JsonPackageReferenceremoved from all 9 affected test/lab.csprojfilesUnifiedCacheFormatTests: JSON object fields in test data files now use.ToJsonString()instead of.GetValue<string>()Testing
net8.0andnet48(1910+ passing, 0 failures)