From 17ba7259b9643553297d944c7a860e53a2569e4d Mon Sep 17 00:00:00 2001 From: Nigusu Yenework Date: Sat, 18 Apr 2026 15:59:01 -0700 Subject: [PATCH 1/3] Add STJ converters --- .../Converters/MetadataFieldStjConverter.cs | 46 ++++ .../MetadataStringOrArrayStjConverter.cs | 42 ++++ .../Converters/NuGetFrameworkStjConverter.cs | 29 +++ .../Converters/NuGetVersionStjConverter.cs | 37 +++ .../PackageDependencyGroupStjConverter.cs | 86 +++++++ .../PackageDependencyStjConverter.cs | 69 ++++++ .../Converters/SafeBoolStjConverter.cs | 37 +++ .../Converters/SafeUriStjConverter.cs | 35 +++ .../Converters/VersionInfoStjConverter.cs | 57 +++++ .../Converters/StjConverterTests.cs | 233 ++++++++++++++++++ 10 files changed, 671 insertions(+) create mode 100644 src/NuGet.Core/NuGet.Protocol/Converters/MetadataFieldStjConverter.cs create mode 100644 src/NuGet.Core/NuGet.Protocol/Converters/MetadataStringOrArrayStjConverter.cs create mode 100644 src/NuGet.Core/NuGet.Protocol/Converters/NuGetFrameworkStjConverter.cs create mode 100644 src/NuGet.Core/NuGet.Protocol/Converters/NuGetVersionStjConverter.cs create mode 100644 src/NuGet.Core/NuGet.Protocol/Converters/PackageDependencyGroupStjConverter.cs create mode 100644 src/NuGet.Core/NuGet.Protocol/Converters/PackageDependencyStjConverter.cs create mode 100644 src/NuGet.Core/NuGet.Protocol/Converters/SafeBoolStjConverter.cs create mode 100644 src/NuGet.Core/NuGet.Protocol/Converters/SafeUriStjConverter.cs create mode 100644 src/NuGet.Core/NuGet.Protocol/Converters/VersionInfoStjConverter.cs create mode 100644 test/NuGet.Core.Tests/NuGet.Protocol.Tests/Converters/StjConverterTests.cs diff --git a/src/NuGet.Core/NuGet.Protocol/Converters/MetadataFieldStjConverter.cs b/src/NuGet.Core/NuGet.Protocol/Converters/MetadataFieldStjConverter.cs new file mode 100644 index 00000000000..71d5623c956 --- /dev/null +++ b/src/NuGet.Core/NuGet.Protocol/Converters/MetadataFieldStjConverter.cs @@ -0,0 +1,46 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace NuGet.Protocol.Converters +{ + /// + /// Reads a JSON string or array of strings into a single comma-separated string. + /// Equivalent to for System.Text.Json. + /// + /// NSJ equivalent: . + internal sealed class MetadataFieldStjConverter : JsonConverter + { + public override bool HandleNull => true; + public override string Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + if (reader.TokenType == JsonTokenType.Null) + { + return string.Empty; + } + + if (reader.TokenType == JsonTokenType.StartArray) + { + var values = new List(); + while (reader.Read() && reader.TokenType != JsonTokenType.EndArray) + { + var s = reader.GetString(); + if (!string.IsNullOrWhiteSpace(s)) + { + values.Add(s!); + } + } + return string.Join(", ", values); + } + + return reader.GetString() ?? string.Empty; + } + + public override void Write(Utf8JsonWriter writer, string value, JsonSerializerOptions options) + => throw new NotSupportedException(); + } +} diff --git a/src/NuGet.Core/NuGet.Protocol/Converters/MetadataStringOrArrayStjConverter.cs b/src/NuGet.Core/NuGet.Protocol/Converters/MetadataStringOrArrayStjConverter.cs new file mode 100644 index 00000000000..1f90f052858 --- /dev/null +++ b/src/NuGet.Core/NuGet.Protocol/Converters/MetadataStringOrArrayStjConverter.cs @@ -0,0 +1,42 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace NuGet.Protocol.Converters +{ + /// + /// Reads a JSON string or array of strings into an of strings. + /// Equivalent to for System.Text.Json. + /// + /// NSJ equivalent: . + internal sealed class MetadataStringOrArrayStjConverter : JsonConverter> + { + public override IReadOnlyList? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + if (reader.TokenType == JsonTokenType.Null) + { + return null; + } + + if (reader.TokenType == JsonTokenType.String) + { + var str = reader.GetString(); + return string.IsNullOrWhiteSpace(str) ? null : new[] { str! }; + } + + var values = new List(); + while (reader.Read() && reader.TokenType != JsonTokenType.EndArray) + { + values.Add(reader.GetString() ?? string.Empty); + } + return values.ToArray(); + } + + public override void Write(Utf8JsonWriter writer, IReadOnlyList value, JsonSerializerOptions options) + => throw new NotSupportedException(); + } +} diff --git a/src/NuGet.Core/NuGet.Protocol/Converters/NuGetFrameworkStjConverter.cs b/src/NuGet.Core/NuGet.Protocol/Converters/NuGetFrameworkStjConverter.cs new file mode 100644 index 00000000000..c9a1e3941a7 --- /dev/null +++ b/src/NuGet.Core/NuGet.Protocol/Converters/NuGetFrameworkStjConverter.cs @@ -0,0 +1,29 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Text.Json; +using System.Text.Json.Serialization; +using NuGet.Frameworks; + +namespace NuGet.Protocol.Converters +{ + /// NSJ equivalent: (registered globally in ). + internal sealed class NuGetFrameworkStjConverter : JsonConverter + { + public override bool HandleNull => true; + public override NuGetFramework Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + if (reader.TokenType == JsonTokenType.Null) + { + return NuGetFramework.AnyFramework; + } + + var value = reader.GetString(); + return string.IsNullOrEmpty(value) ? NuGetFramework.AnyFramework : NuGetFramework.Parse(value!); + } + + public override void Write(Utf8JsonWriter writer, NuGetFramework value, JsonSerializerOptions options) + => writer.WriteStringValue(value.GetShortFolderName()); + } +} diff --git a/src/NuGet.Core/NuGet.Protocol/Converters/NuGetVersionStjConverter.cs b/src/NuGet.Core/NuGet.Protocol/Converters/NuGetVersionStjConverter.cs new file mode 100644 index 00000000000..9ce8549dcbd --- /dev/null +++ b/src/NuGet.Core/NuGet.Protocol/Converters/NuGetVersionStjConverter.cs @@ -0,0 +1,37 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Text.Json; +using System.Text.Json.Serialization; +using NuGet.Versioning; + +namespace NuGet.Protocol.Converters +{ + /// NSJ equivalent: (registered globally in ). + internal sealed class NuGetVersionStjConverter : JsonConverter + { + public override NuGetVersion? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + if (reader.TokenType == JsonTokenType.Null) + { + return null; + } + + var str = reader.GetString(); + return str is null ? null : NuGetVersion.Parse(str); + } + + public override void Write(Utf8JsonWriter writer, NuGetVersion value, JsonSerializerOptions options) + { + if (value is null) + { + writer.WriteNullValue(); + } + else + { + writer.WriteStringValue(value.ToString()); + } + } + } +} diff --git a/src/NuGet.Core/NuGet.Protocol/Converters/PackageDependencyGroupStjConverter.cs b/src/NuGet.Core/NuGet.Protocol/Converters/PackageDependencyGroupStjConverter.cs new file mode 100644 index 00000000000..86fc6f41d08 --- /dev/null +++ b/src/NuGet.Core/NuGet.Protocol/Converters/PackageDependencyGroupStjConverter.cs @@ -0,0 +1,86 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Text.Json; +using System.Text.Json.Serialization; +using NuGet.Frameworks; +using NuGet.Packaging; +using NuGet.Packaging.Core; + +namespace NuGet.Protocol.Converters +{ + /// + /// No explicit NSJ equivalent — NSJ relies on [JsonConstructor] and [JsonProperty] attributes + /// on in NuGet.Packaging. STJ ignores those attributes, requiring this converter. + /// + internal sealed class PackageDependencyGroupStjConverter : JsonConverter + { + private static readonly PackageDependencyStjConverter _dependencyConverter = new(); + + public override PackageDependencyGroup Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + if (reader.TokenType != JsonTokenType.StartObject) + { + throw new JsonException(); + } + + NuGetFramework? targetFramework = null; + var packages = new List(); + + while (reader.Read() && reader.TokenType != JsonTokenType.EndObject) + { + if (reader.TokenType != JsonTokenType.PropertyName) + { + continue; + } + + var propName = reader.GetString(); + reader.Read(); + + if (string.Equals(propName, JsonProperties.TargetFramework, StringComparison.OrdinalIgnoreCase)) + { + if (reader.TokenType != JsonTokenType.Null) + { + var fw = reader.GetString(); + targetFramework = string.IsNullOrEmpty(fw) ? null : NuGetFramework.Parse(fw!); + } + } + else if (string.Equals(propName, JsonProperties.Dependencies, StringComparison.OrdinalIgnoreCase)) + { + if (reader.TokenType == JsonTokenType.StartArray) + { + while (reader.Read() && reader.TokenType != JsonTokenType.EndArray) + { + packages.Add(_dependencyConverter.Read(ref reader, typeof(PackageDependency), options)); + } + } + else + { + reader.Skip(); + } + } + else + { + reader.Skip(); + } + } + + return new PackageDependencyGroup(targetFramework ?? NuGetFramework.AnyFramework, packages); + } + + public override void Write(Utf8JsonWriter writer, PackageDependencyGroup value, JsonSerializerOptions options) + { + writer.WriteStartObject(); + writer.WriteString(JsonProperties.TargetFramework, value.TargetFramework.GetShortFolderName()); + writer.WriteStartArray(JsonProperties.Dependencies); + foreach (var pkg in value.Packages) + { + _dependencyConverter.Write(writer, pkg, options); + } + writer.WriteEndArray(); + writer.WriteEndObject(); + } + } +} diff --git a/src/NuGet.Core/NuGet.Protocol/Converters/PackageDependencyStjConverter.cs b/src/NuGet.Core/NuGet.Protocol/Converters/PackageDependencyStjConverter.cs new file mode 100644 index 00000000000..d751344d5e8 --- /dev/null +++ b/src/NuGet.Core/NuGet.Protocol/Converters/PackageDependencyStjConverter.cs @@ -0,0 +1,69 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Text.Json; +using System.Text.Json.Serialization; +using NuGet.Packaging.Core; +using NuGet.Versioning; + +namespace NuGet.Protocol.Converters +{ + /// + /// No explicit NSJ equivalent — NSJ relies on [JsonConstructor] and [JsonProperty] attributes + /// on in NuGet.Packaging. STJ ignores those attributes, requiring this converter. + /// + internal sealed class PackageDependencyStjConverter : JsonConverter + { + private static readonly VersionRangeStjConverter _versionRangeConverter = new(); + + public override PackageDependency Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + if (reader.TokenType != JsonTokenType.StartObject) + { + throw new JsonException(); + } + + string? id = null; + VersionRange? range = null; + + while (reader.Read() && reader.TokenType != JsonTokenType.EndObject) + { + if (reader.TokenType != JsonTokenType.PropertyName) + { + continue; + } + + var propName = reader.GetString(); + reader.Read(); + + if (string.Equals(propName, JsonProperties.PackageId, StringComparison.OrdinalIgnoreCase)) + { + id = reader.GetString(); + } + else if (string.Equals(propName, JsonProperties.Range, StringComparison.OrdinalIgnoreCase)) + { + if (reader.TokenType != JsonTokenType.Null) + { + range = _versionRangeConverter.Read(ref reader, typeof(VersionRange), options); + } + } + else + { + reader.Skip(); + } + } + + return new PackageDependency(id!, range); + } + + public override void Write(Utf8JsonWriter writer, PackageDependency value, JsonSerializerOptions options) + { + writer.WriteStartObject(); + writer.WriteString(JsonProperties.PackageId, value.Id); + writer.WritePropertyName(JsonProperties.Range); + _versionRangeConverter.Write(writer, value.VersionRange, options); + writer.WriteEndObject(); + } + } +} diff --git a/src/NuGet.Core/NuGet.Protocol/Converters/SafeBoolStjConverter.cs b/src/NuGet.Core/NuGet.Protocol/Converters/SafeBoolStjConverter.cs new file mode 100644 index 00000000000..8fb3592ebbb --- /dev/null +++ b/src/NuGet.Core/NuGet.Protocol/Converters/SafeBoolStjConverter.cs @@ -0,0 +1,37 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace NuGet.Protocol.Converters +{ + /// NSJ equivalent: . + internal sealed class SafeBoolStjConverter : JsonConverter + { + public override bool Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + switch (reader.TokenType) + { + case JsonTokenType.True: + return true; + case JsonTokenType.False: + case JsonTokenType.Null: + return false; + case JsonTokenType.String: + return bool.TryParse(reader.GetString()?.Trim(), out bool flag) && flag; + case JsonTokenType.Number: + return reader.TryGetInt64(out long l) && l == 1; + default: + reader.Skip(); + return false; + } + } + + public override void Write(Utf8JsonWriter writer, bool value, JsonSerializerOptions options) + { + throw new NotSupportedException(); + } + } +} diff --git a/src/NuGet.Core/NuGet.Protocol/Converters/SafeUriStjConverter.cs b/src/NuGet.Core/NuGet.Protocol/Converters/SafeUriStjConverter.cs new file mode 100644 index 00000000000..0862c8cbba4 --- /dev/null +++ b/src/NuGet.Core/NuGet.Protocol/Converters/SafeUriStjConverter.cs @@ -0,0 +1,35 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace NuGet.Protocol.Converters +{ + /// NSJ equivalent: . + internal sealed class SafeUriStjConverter : JsonConverter + { + public override Uri? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + if (reader.TokenType == JsonTokenType.Null) + { + return null; + } + + if (reader.TokenType == JsonTokenType.String) + { + Uri.TryCreate(reader.GetString()?.Trim(), UriKind.Absolute, out Uri? uri); + return uri; + } + + reader.Skip(); + return null; + } + + public override void Write(Utf8JsonWriter writer, Uri value, JsonSerializerOptions options) + { + throw new NotSupportedException(); + } + } +} diff --git a/src/NuGet.Core/NuGet.Protocol/Converters/VersionInfoStjConverter.cs b/src/NuGet.Core/NuGet.Protocol/Converters/VersionInfoStjConverter.cs new file mode 100644 index 00000000000..a09cc539b59 --- /dev/null +++ b/src/NuGet.Core/NuGet.Protocol/Converters/VersionInfoStjConverter.cs @@ -0,0 +1,57 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Text.Json; +using System.Text.Json.Serialization; +using NuGet.Protocol.Core.Types; +using NuGet.Versioning; + +namespace NuGet.Protocol.Converters +{ + /// NSJ equivalent: (registered globally in ). + internal sealed class VersionInfoStjConverter : JsonConverter + { + public override VersionInfo Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + if (reader.TokenType != JsonTokenType.StartObject) + { + throw new JsonException(); + } + + string? version = null; + long? downloads = null; + + while (reader.Read() && reader.TokenType != JsonTokenType.EndObject) + { + if (reader.TokenType != JsonTokenType.PropertyName) + { + continue; + } + + var propName = reader.GetString(); + reader.Read(); + + if (string.Equals(propName, JsonProperties.Version, StringComparison.OrdinalIgnoreCase)) + { + version = reader.GetString(); + } + else if (string.Equals(propName, "downloads", StringComparison.OrdinalIgnoreCase)) + { + downloads = reader.TokenType == JsonTokenType.Null ? null : reader.GetInt64(); + } + else + { + reader.Skip(); + } + } + + return new VersionInfo(NuGetVersion.Parse(version!), downloads); + } + + public override void Write(Utf8JsonWriter writer, VersionInfo value, JsonSerializerOptions options) + { + throw new NotSupportedException(); + } + } +} diff --git a/test/NuGet.Core.Tests/NuGet.Protocol.Tests/Converters/StjConverterTests.cs b/test/NuGet.Core.Tests/NuGet.Protocol.Tests/Converters/StjConverterTests.cs new file mode 100644 index 00000000000..2cc0fcff74b --- /dev/null +++ b/test/NuGet.Core.Tests/NuGet.Protocol.Tests/Converters/StjConverterTests.cs @@ -0,0 +1,233 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Text.Json; +using System.Text.Json.Serialization; +using FluentAssertions; +using NuGet.Frameworks; +using NuGet.Packaging; +using NuGet.Packaging.Core; +using NuGet.Protocol.Converters; +using NuGet.Protocol.Core.Types; +using NuGet.Versioning; +using Xunit; + +namespace NuGet.Protocol.Tests.Converters +{ + public class StjConverterTests + { + [Theory] + [InlineData("true", true)] + [InlineData("false", false)] + [InlineData("null", false)] + [InlineData("1", true)] + [InlineData("0", false)] + [InlineData("\"true\"", true)] + [InlineData("\"True\"", true)] + [InlineData("\"false\"", false)] + [InlineData("\"invalid\"", false)] + [InlineData("\" \"", false)] + [InlineData("{}", false)] + public void SafeBoolStjConverter_OnVariousInputs_ReturnsCorrectBool(string json, bool expected) + { + // Act + bool actual = Deserialize(json, new SafeBoolStjConverter()); + + // Assert + actual.Should().Be(expected); + } + + [Theory] + [InlineData("\"https://contoso.test/path\"", "https://contoso.test/path")] + [InlineData("\"not a uri\"", null)] + [InlineData("null", null)] + [InlineData("{}", null)] + public void SafeUriStjConverter_OnVariousInputs_ReturnsCorrectUri(string json, string? expectedUri) + { + // Act + var actual = Deserialize(json, new SafeUriStjConverter()); + + // Assert + actual?.OriginalString.Should().Be(expectedUri); + } + + [Theory] + [InlineData("\"1.2.3\"", "1.2.3")] + [InlineData("\"1.0.0-beta.1\"", "1.0.0-beta.1")] + [InlineData("null", null)] + public void NuGetVersionStjConverter_OnVersionString_ReturnsCorrectVersion(string json, string? expectedVersion) + { + // Act + var actual = Deserialize(json, new NuGetVersionStjConverter()); + + // Assert + actual?.ToString().Should().Be(expectedVersion); + } + + [Fact] + public void NuGetVersionStjConverter_OnRoundTrip_PreservesVersion() + { + // Arrange + var version = new NuGetVersion(1, 2, 3, "beta.1"); + var options = OptionsFor(new NuGetVersionStjConverter()); + + // Act + var json = JsonSerializer.Serialize(version, options); + var actual = JsonSerializer.Deserialize(json, options); + + // Assert + actual.Should().Be(version); + } + + [Theory] + [InlineData("\"author\"", "author")] + [InlineData("null", "")] + [InlineData("[\"Alice\",\"Bob\",\"Charlie\"]", "Alice, Bob, Charlie")] + [InlineData("[\"Alice\",\" \",\"\",\"Bob\"]", "Alice, Bob")] + public void MetadataFieldStjConverter_OnStringOrArray_ReturnsCorrectJoinedString(string json, string expected) + { + // Act + var actual = Deserialize(json, new MetadataFieldStjConverter()); + + // Assert + actual.Should().Be(expected); + } + + [Theory] + [InlineData("\"owner\"", new[] { "owner" })] + [InlineData("[\"a\",\"b\",\"c\"]", new[] { "a", "b", "c" })] + public void MetadataStringOrArrayStjConverter_OnStringOrArray_ReturnsCorrectItems(string json, string[] expected) + { + // Act + var actual = Deserialize>(json, new MetadataStringOrArrayStjConverter()); + + // Assert + actual.Should().Equal(expected); + } + + [Theory] + [InlineData("null")] + [InlineData("\" \"")] + public void MetadataStringOrArrayStjConverter_OnNullOrWhitespace_ReturnsNull(string json) + { + // Act + var actual = Deserialize>(json, new MetadataStringOrArrayStjConverter()); + + // Assert + actual.Should().BeNull(); + } + + [Theory] + [InlineData("""{"version":"1.0.0","downloads":12345}""", "1.0.0", 12345L)] + [InlineData("""{"version":"2.0.0-beta"}""", "2.0.0-beta", null)] + public void VersionInfoStjConverter_OnObject_ReturnsCorrectVersionInfo(string json, string expectedVersion, long? expectedDownloads) + { + // Act + var actual = Deserialize(json, new VersionInfoStjConverter()); + + // Assert + actual.Version.Should().Be(NuGetVersion.Parse(expectedVersion)); + actual.DownloadCount.Should().Be(expectedDownloads); + } + + [Theory] + [InlineData("\"net472\"", "net472")] + [InlineData("\"net8.0\"", "net8.0")] + [InlineData("null", null)] + [InlineData("\"\"", null)] + public void NuGetFrameworkStjConverter_OnFrameworkString_ReturnsFramework(string json, string? expectedFramework) + { + // Arrange + var expected = expectedFramework is null ? NuGetFramework.AnyFramework : NuGetFramework.Parse(expectedFramework); + + // Act + var actual = Deserialize(json, new NuGetFrameworkStjConverter()); + + // Assert + actual.Should().Be(expected); + } + + [Theory] + [InlineData("""{"id":"Newtonsoft.Json","range":"[6.0.0, )"}""", "Newtonsoft.Json", "[6.0.0, )")] + [InlineData("""{"id":"SomePackage"}""", "SomePackage", null)] + [InlineData("""{"Id":"MyPackage","Range":"[1.0.0, 2.0.0)"}""", "MyPackage", "[1.0.0, 2.0.0)")] + public void PackageDependencyStjConverter_OnObject_ReturnsCorrectDependency(string json, string expectedId, string? expectedRange) + { + // Arrange + var expectedVersionRange = expectedRange is null ? VersionRange.All : VersionRange.Parse(expectedRange); + + // Act + var actual = Deserialize(json, new PackageDependencyStjConverter()); + + // Assert + actual.Id.Should().Be(expectedId); + actual.VersionRange.Should().Be(expectedVersionRange); + } + + [Fact] + public void PackageDependencyStjConverter_OnRoundTrip_PreservesValues() + { + // Arrange + var original = new PackageDependency("Newtonsoft.Json", VersionRange.Parse("[13.0.0, )")); + var options = OptionsFor(new PackageDependencyStjConverter()); + + // Act + var json = JsonSerializer.Serialize(original, options); + var actual = JsonSerializer.Deserialize(json, options); + + // Assert + actual!.Id.Should().Be(original.Id); + actual.VersionRange.Should().Be(original.VersionRange); + } + + [Theory] + [InlineData("""{"targetFramework":"net8.0","dependencies":[{"id":"Serilog","range":"[3.0.0, )"}]}""", "net8.0", 1)] + [InlineData("""{"targetFramework":"net472","dependencies":[]}""", "net472", 0)] + [InlineData("""{"targetFramework":null,"dependencies":[]}""", null, 0)] + public void PackageDependencyGroupStjConverter_OnObject_ReturnsCorrectGroup(string json, string? expectedFramework, int expectedCount) + { + // Arrange + var expectedTfm = expectedFramework is null ? NuGetFramework.AnyFramework : NuGetFramework.Parse(expectedFramework); + + // Act + var actual = Deserialize(json, new PackageDependencyGroupStjConverter()); + + // Assert + actual.TargetFramework.Should().Be(expectedTfm); + actual.Packages.Should().HaveCount(expectedCount); + } + + [Fact] + public void PackageDependencyGroupStjConverter_OnRoundTrip_PreservesValues() + { + // Arrange + var original = new PackageDependencyGroup( + NuGetFramework.Parse("net8.0"), + new[] { new PackageDependency("Serilog", VersionRange.Parse("[3.0.0, )")) }); + var options = OptionsFor(new PackageDependencyGroupStjConverter()); + + // Act + var json = JsonSerializer.Serialize(original, options); + var actual = JsonSerializer.Deserialize(json, options); + + // Assert + actual!.TargetFramework.Should().Be(original.TargetFramework); + actual.Packages.Should().ContainSingle().Which.Id.Should().Be("Serilog"); + } + + private static T Deserialize(string json, params JsonConverter[] converters) + => JsonSerializer.Deserialize(json, OptionsFor(converters))!; + + private static JsonSerializerOptions OptionsFor(params JsonConverter[] converters) + { + var options = new JsonSerializerOptions(); + foreach (var c in converters) + { + options.Converters.Add(c); + } + return options; + } + } +} From aef8cd93067531d49cec7b1aff523093f0cfb6b7 Mon Sep 17 00:00:00 2001 From: Nigusu Yenework Date: Mon, 20 Apr 2026 11:33:01 -0700 Subject: [PATCH 2/3] Migrate Registration --- .../Converters/PackageMetadataJsonContext.cs | 25 ++++ .../NuGet.Protocol/GlobalSuppressions.cs | 1 - .../Model/AlternatePackageMetadata.cs | 10 +- .../Model/PackageDeprecationMetadata.cs | 11 +- .../Model/PackageSearchMetadata.cs | 134 ++++++++++-------- .../PackageSearchMetadataRegistration.cs | 9 +- .../Model/PackageVulnerabilityMetadata.cs | 10 +- .../NuGet.Protocol/Model/RegistrationIndex.cs | 4 +- .../Model/RegistrationLeafItem.cs | 6 +- .../NuGet.Protocol/Model/RegistrationPage.cs | 10 +- .../Resources/PackageMetadataResourceV3.cs | 21 ++- 11 files changed, 148 insertions(+), 93 deletions(-) create mode 100644 src/NuGet.Core/NuGet.Protocol/Converters/PackageMetadataJsonContext.cs diff --git a/src/NuGet.Core/NuGet.Protocol/Converters/PackageMetadataJsonContext.cs b/src/NuGet.Core/NuGet.Protocol/Converters/PackageMetadataJsonContext.cs new file mode 100644 index 00000000000..d181652b0d4 --- /dev/null +++ b/src/NuGet.Core/NuGet.Protocol/Converters/PackageMetadataJsonContext.cs @@ -0,0 +1,25 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Text.Json.Serialization; +using NuGet.Protocol.Model; + +namespace NuGet.Protocol.Converters +{ + [JsonSourceGenerationOptions( + PropertyNameCaseInsensitive = true, + GenerationMode = JsonSourceGenerationMode.Metadata, + Converters = new[] + { + typeof(NuGetVersionStjConverter), + typeof(VersionInfoStjConverter), + typeof(PackageDependencyGroupStjConverter), + typeof(PackageDependencyStjConverter), + typeof(VersionRangeStjConverter) + })] + [JsonSerializable(typeof(RegistrationIndex))] + [JsonSerializable(typeof(RegistrationPage))] + internal partial class PackageMetadataJsonContext : JsonSerializerContext + { + } +} diff --git a/src/NuGet.Core/NuGet.Protocol/GlobalSuppressions.cs b/src/NuGet.Core/NuGet.Protocol/GlobalSuppressions.cs index 82af3578438..7adff1fc882 100644 --- a/src/NuGet.Core/NuGet.Protocol/GlobalSuppressions.cs +++ b/src/NuGet.Core/NuGet.Protocol/GlobalSuppressions.cs @@ -232,7 +232,6 @@ [assembly: SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "", Scope = "member", Target = "~M:NuGet.Protocol.Core.Types.PluginFindPackageByIdResource.AddOrUpdateLogger(NuGet.Protocol.Plugins.IPlugin,NuGet.Common.ILogger)")] [assembly: SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "", Scope = "member", Target = "~M:NuGet.Protocol.DownloadResourcePlugin.AddOrUpdateLogger(NuGet.Protocol.Plugins.IPlugin,NuGet.Common.ILogger)")] [assembly: SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "", Scope = "member", Target = "~M:NuGet.Protocol.HttpSourceAuthenticationHandler.CredentialsSuccessfullyUsed(System.Uri,System.Net.ICredentials)")] -[assembly: SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "", Scope = "member", Target = "~M:NuGet.Protocol.PackageMetadataResourceV3.DeserializeStreamDataAsync``1(System.IO.Stream,System.Threading.CancellationToken)~System.Threading.Tasks.Task{``0}")] [assembly: SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "", Scope = "member", Target = "~M:NuGet.Protocol.PackageMetadataResourceV3.LoadRegistrationIndexAsync(NuGet.Protocol.HttpSource,System.Uri,System.String,NuGet.Protocol.Core.Types.SourceCacheContext,System.Func{NuGet.Protocol.HttpSourceResult,System.Threading.Tasks.Task{NuGet.Protocol.Model.RegistrationIndex}},NuGet.Common.ILogger,System.Threading.CancellationToken)~System.Threading.Tasks.Task{System.ValueTuple{NuGet.Protocol.Model.RegistrationIndex,NuGet.Protocol.Core.Types.HttpSourceCacheContext}}")] [assembly: SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "", Scope = "member", Target = "~M:NuGet.Protocol.PackageSearchResourceV3.ProcessHttpStreamWithoutBufferingAsync(System.Net.Http.HttpResponseMessage,System.UInt32,System.Threading.CancellationToken)~System.Threading.Tasks.Task{NuGet.Protocol.Model.V3SearchResults}")] [assembly: SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "", Scope = "member", Target = "~M:NuGet.Protocol.Plugins.MessageDispatcher.HandleInboundFault(NuGet.Protocol.Plugins.Message)")] diff --git a/src/NuGet.Core/NuGet.Protocol/Model/AlternatePackageMetadata.cs b/src/NuGet.Core/NuGet.Protocol/Model/AlternatePackageMetadata.cs index a6a3f3ad1a9..70a360e8cf6 100644 --- a/src/NuGet.Core/NuGet.Protocol/Model/AlternatePackageMetadata.cs +++ b/src/NuGet.Core/NuGet.Protocol/Model/AlternatePackageMetadata.cs @@ -1,17 +1,21 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using Newtonsoft.Json; +using System.Text.Json.Serialization; +using NuGet.Protocol.Converters; using NuGet.Versioning; namespace NuGet.Protocol { public class AlternatePackageMetadata { - [JsonProperty(PropertyName = JsonProperties.PackageId)] + [JsonPropertyName(JsonProperties.PackageId)] + [JsonInclude] public string? PackageId { get; internal set; } - [JsonProperty(PropertyName = JsonProperties.Range, ItemConverterType = typeof(VersionRangeConverter))] + [JsonPropertyName(JsonProperties.Range)] + [JsonConverter(typeof(VersionRangeStjConverter))] + [JsonInclude] public VersionRange? Range { get; internal set; } } } diff --git a/src/NuGet.Core/NuGet.Protocol/Model/PackageDeprecationMetadata.cs b/src/NuGet.Core/NuGet.Protocol/Model/PackageDeprecationMetadata.cs index f7a7777204d..69c1dff015f 100644 --- a/src/NuGet.Core/NuGet.Protocol/Model/PackageDeprecationMetadata.cs +++ b/src/NuGet.Core/NuGet.Protocol/Model/PackageDeprecationMetadata.cs @@ -3,19 +3,22 @@ using System; using System.Collections.Generic; -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace NuGet.Protocol { public class PackageDeprecationMetadata { - [JsonProperty(PropertyName = JsonProperties.DeprecationMessage)] + [JsonPropertyName(JsonProperties.DeprecationMessage)] + [JsonInclude] public string? Message { get; internal set; } - [JsonProperty(PropertyName = JsonProperties.DeprecationReasons)] + [JsonPropertyName(JsonProperties.DeprecationReasons)] + [JsonInclude] public IEnumerable Reasons { get; internal set; } = Array.Empty(); - [JsonProperty(PropertyName = JsonProperties.AlternatePackage)] + [JsonPropertyName(JsonProperties.AlternatePackage)] + [JsonInclude] public AlternatePackageMetadata? AlternatePackage { get; internal set; } } } diff --git a/src/NuGet.Core/NuGet.Protocol/Model/PackageSearchMetadata.cs b/src/NuGet.Core/NuGet.Protocol/Model/PackageSearchMetadata.cs index fb702f8e472..188fc9abb52 100644 --- a/src/NuGet.Core/NuGet.Protocol/Model/PackageSearchMetadata.cs +++ b/src/NuGet.Core/NuGet.Protocol/Model/PackageSearchMetadata.cs @@ -5,14 +5,14 @@ using System; using System.Collections.Generic; -using System.ComponentModel; using System.Globalization; using System.Linq; +using System.Text.Json.Serialization; using System.Threading.Tasks; -using Newtonsoft.Json; using NuGet.Packaging; using NuGet.Packaging.Core; using NuGet.Packaging.Licenses; +using NuGet.Protocol.Converters; using NuGet.Protocol.Core.Types; using NuGet.Versioning; @@ -20,12 +20,14 @@ namespace NuGet.Protocol { public class PackageSearchMetadata : IPackageSearchMetadata { - [JsonProperty(PropertyName = JsonProperties.Authors)] - [JsonConverter(typeof(MetadataFieldConverter))] - public string Authors { get; private set; } + [JsonPropertyName(JsonProperties.Authors)] + [JsonConverter(typeof(MetadataFieldStjConverter))] + [JsonInclude] + public string Authors { get; internal set; } - [JsonProperty(PropertyName = JsonProperties.DependencyGroups)] - public IEnumerable DependencySetsInternal { get; private set; } + [JsonPropertyName(JsonProperties.DependencyGroups)] + [JsonInclude] + public IEnumerable DependencySetsInternal { get; internal set; } [JsonIgnore] public IEnumerable DependencySets @@ -36,14 +38,17 @@ public IEnumerable DependencySets } } - [JsonProperty(PropertyName = JsonProperties.Description)] - public string Description { get; private set; } + [JsonPropertyName(JsonProperties.Description)] + [JsonInclude] + public string Description { get; internal set; } - [JsonProperty(PropertyName = JsonProperties.DownloadCount)] - public long? DownloadCount { get; private set; } + [JsonPropertyName(JsonProperties.DownloadCount)] + [JsonInclude] + public long? DownloadCount { get; internal set; } - [JsonProperty(PropertyName = JsonProperties.IconUrl)] - public Uri IconUrl { get; private set; } + [JsonPropertyName(JsonProperties.IconUrl)] + [JsonInclude] + public Uri IconUrl { get; internal set; } private PackageIdentity _packageIdentity = null; @@ -60,18 +65,20 @@ public PackageIdentity Identity } } - [JsonProperty(PropertyName = JsonProperties.LicenseUrl)] - [JsonConverter(typeof(SafeUriConverter))] - public Uri LicenseUrl { get; private set; } + [JsonPropertyName(JsonProperties.LicenseUrl)] + [JsonConverter(typeof(SafeUriStjConverter))] + [JsonInclude] + public Uri LicenseUrl { get; internal set; } private IReadOnlyList _ownersList; - [JsonProperty(PropertyName = JsonProperties.Owners)] - [JsonConverter(typeof(MetadataStringOrArrayConverter))] + [JsonPropertyName(JsonProperties.Owners)] + [JsonConverter(typeof(MetadataStringOrArrayStjConverter))] + [JsonInclude] public IReadOnlyList OwnersList { get { return _ownersList; } - private set + internal set { if (_ownersList != value) { @@ -94,19 +101,23 @@ public string Owners } } - [JsonProperty(PropertyName = JsonProperties.PackageId)] - public string PackageId { get; private set; } + [JsonPropertyName(JsonProperties.PackageId)] + [JsonInclude] + public string PackageId { get; internal set; } - [JsonProperty(PropertyName = JsonProperties.ProjectUrl)] - [JsonConverter(typeof(SafeUriConverter))] - public Uri ProjectUrl { get; private set; } + [JsonPropertyName(JsonProperties.ProjectUrl)] + [JsonConverter(typeof(SafeUriStjConverter))] + [JsonInclude] + public Uri ProjectUrl { get; internal set; } - [JsonProperty(PropertyName = JsonProperties.Published)] - public DateTimeOffset? Published { get; private set; } + [JsonPropertyName(JsonProperties.Published)] + [JsonInclude] + public DateTimeOffset? Published { get; internal set; } - [JsonProperty(PropertyName = JsonProperties.ReadmeUrl)] - [JsonConverter(typeof(SafeUriConverter))] - public Uri ReadmeUrl { get; private set; } + [JsonPropertyName(JsonProperties.ReadmeUrl)] + [JsonConverter(typeof(SafeUriStjConverter))] + [JsonInclude] + public Uri ReadmeUrl { get; internal set; } [JsonIgnore] public string ReadmeFileUrl { get; internal set; } @@ -117,47 +128,55 @@ public string Owners [JsonIgnore] public Uri PackageDetailsUrl { get; set; } - [JsonProperty(PropertyName = JsonProperties.RequireLicenseAcceptance, DefaultValueHandling = DefaultValueHandling.Populate)] - [DefaultValue(false)] - [JsonConverter(typeof(SafeBoolConverter))] - public bool RequireLicenseAcceptance { get; private set; } + [JsonPropertyName(JsonProperties.RequireLicenseAcceptance)] + [JsonConverter(typeof(SafeBoolStjConverter))] + [JsonInclude] + public bool RequireLicenseAcceptance { get; internal set; } private string _summaryValue; - [JsonProperty(PropertyName = JsonProperties.Summary)] + [JsonPropertyName(JsonProperties.Summary)] + [JsonInclude] public string Summary { get { return !string.IsNullOrEmpty(_summaryValue) ? _summaryValue : Description; } - private set { _summaryValue = value; } + internal set { _summaryValue = value; } } - [JsonProperty(PropertyName = JsonProperties.Tags)] - [JsonConverter(typeof(MetadataFieldConverter))] - public string Tags { get; private set; } + [JsonPropertyName(JsonProperties.Tags)] + [JsonConverter(typeof(MetadataFieldStjConverter))] + [JsonInclude] + public string Tags { get; internal set; } private string _titleValue; - [JsonProperty(PropertyName = JsonProperties.Title)] + [JsonPropertyName(JsonProperties.Title)] + [JsonInclude] public string Title { get { return !string.IsNullOrEmpty(_titleValue) ? _titleValue : PackageId; } - private set { _titleValue = value; } + internal set { _titleValue = value; } } - [JsonProperty(PropertyName = JsonProperties.Version)] - public NuGetVersion Version { get; private set; } + [JsonPropertyName(JsonProperties.Version)] + [JsonInclude] + public NuGetVersion Version { get; internal set; } - [JsonProperty(PropertyName = JsonProperties.Versions)] - public VersionInfo[] ParsedVersions { get; private set; } + [JsonPropertyName(JsonProperties.Versions)] + [JsonInclude] + public VersionInfo[] ParsedVersions { get; internal set; } - [JsonProperty(PropertyName = JsonProperties.PrefixReserved)] - public bool PrefixReserved { get; private set; } + [JsonPropertyName(JsonProperties.PrefixReserved)] + [JsonInclude] + public bool PrefixReserved { get; internal set; } - [JsonProperty(PropertyName = JsonProperties.LicenseExpression)] - public string LicenseExpression { get; private set; } + [JsonPropertyName(JsonProperties.LicenseExpression)] + [JsonInclude] + public string LicenseExpression { get; internal set; } - [JsonProperty(PropertyName = JsonProperties.LicenseExpressionVersion)] - public string LicenseExpressionVersion { get; private set; } + [JsonPropertyName(JsonProperties.LicenseExpressionVersion)] + [JsonInclude] + public string LicenseExpressionVersion { get; internal set; } [JsonIgnore] public LicenseMetadata LicenseMetadata @@ -244,18 +263,21 @@ private static IList GetNonStandardLicenseIdentifiers(NuGetLicenseExpres /// public Task> GetVersionsAsync() => Task.FromResult>(ParsedVersions); - [JsonProperty(PropertyName = JsonProperties.Listed)] - public bool IsListed { get; private set; } = true; + [JsonPropertyName(JsonProperties.Listed)] + [JsonInclude] + public bool IsListed { get; internal set; } = true; - [JsonProperty(PropertyName = JsonProperties.Deprecation)] - public PackageDeprecationMetadata DeprecationMetadata { get; private set; } + [JsonPropertyName(JsonProperties.Deprecation)] + [JsonInclude] + public PackageDeprecationMetadata DeprecationMetadata { get; internal set; } /// public Task GetDeprecationMetadataAsync() => Task.FromResult(DeprecationMetadata); /// - [JsonProperty(PropertyName = JsonProperties.Vulnerabilities)] - public IEnumerable Vulnerabilities { get; private set; } + [JsonPropertyName(JsonProperties.Vulnerabilities)] + [JsonInclude] + public IEnumerable Vulnerabilities { get; internal set; } internal void CacheStrings(MetadataReferenceCache cache) { diff --git a/src/NuGet.Core/NuGet.Protocol/Model/PackageSearchMetadataRegistration.cs b/src/NuGet.Core/NuGet.Protocol/Model/PackageSearchMetadataRegistration.cs index 9d7e6990586..3f6b431b3ab 100644 --- a/src/NuGet.Core/NuGet.Protocol/Model/PackageSearchMetadataRegistration.cs +++ b/src/NuGet.Core/NuGet.Protocol/Model/PackageSearchMetadataRegistration.cs @@ -4,7 +4,8 @@ #nullable disable using System; -using Newtonsoft.Json; +using System.Text.Json.Serialization; +using NuGet.Protocol.Converters; namespace NuGet.Protocol { @@ -17,7 +18,9 @@ public class PackageSearchMetadataRegistration : PackageSearchMetadata /// /// The of this package in the catalog. /// - [JsonProperty(PropertyName = JsonProperties.SubjectId)] - public Uri CatalogUri { get; private set; } + [JsonPropertyName(JsonProperties.SubjectId)] + [JsonConverter(typeof(SafeUriStjConverter))] + [JsonInclude] + public Uri CatalogUri { get; internal set; } } } diff --git a/src/NuGet.Core/NuGet.Protocol/Model/PackageVulnerabilityMetadata.cs b/src/NuGet.Core/NuGet.Protocol/Model/PackageVulnerabilityMetadata.cs index 70b211a1774..7789c5190da 100644 --- a/src/NuGet.Core/NuGet.Protocol/Model/PackageVulnerabilityMetadata.cs +++ b/src/NuGet.Core/NuGet.Protocol/Model/PackageVulnerabilityMetadata.cs @@ -4,16 +4,20 @@ #nullable disable using System; -using Newtonsoft.Json; +using System.Text.Json.Serialization; +using NuGet.Protocol.Converters; namespace NuGet.Protocol { public class PackageVulnerabilityMetadata { - [JsonProperty(PropertyName = JsonProperties.AdvisoryUrl, ItemConverterType = typeof(SafeUriConverter))] + [JsonPropertyName(JsonProperties.AdvisoryUrl)] + [JsonConverter(typeof(SafeUriStjConverter))] + [JsonInclude] public Uri AdvisoryUrl { get; internal set; } - [JsonProperty(PropertyName = JsonProperties.Severity)] + [JsonPropertyName(JsonProperties.Severity)] + [JsonInclude] public int Severity { get; internal set; } public PackageVulnerabilityMetadata(Uri advisoryUrl, int severity) diff --git a/src/NuGet.Core/NuGet.Protocol/Model/RegistrationIndex.cs b/src/NuGet.Core/NuGet.Protocol/Model/RegistrationIndex.cs index 9dff1fa35f8..c861a54a2f6 100644 --- a/src/NuGet.Core/NuGet.Protocol/Model/RegistrationIndex.cs +++ b/src/NuGet.Core/NuGet.Protocol/Model/RegistrationIndex.cs @@ -4,7 +4,7 @@ #nullable disable using System.Collections.Generic; -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace NuGet.Protocol.Model { @@ -13,7 +13,7 @@ namespace NuGet.Protocol.Model /// internal class RegistrationIndex { - [JsonProperty("items")] + [JsonPropertyName("items")] public List Items { get; set; } } } diff --git a/src/NuGet.Core/NuGet.Protocol/Model/RegistrationLeafItem.cs b/src/NuGet.Core/NuGet.Protocol/Model/RegistrationLeafItem.cs index 955a373c77e..f39317a5dfc 100644 --- a/src/NuGet.Core/NuGet.Protocol/Model/RegistrationLeafItem.cs +++ b/src/NuGet.Core/NuGet.Protocol/Model/RegistrationLeafItem.cs @@ -4,7 +4,7 @@ #nullable disable using System; -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace NuGet.Protocol.Model { @@ -13,10 +13,10 @@ namespace NuGet.Protocol.Model /// internal class RegistrationLeafItem { - [JsonProperty("catalogEntry")] + [JsonPropertyName("catalogEntry")] public PackageSearchMetadataRegistration CatalogEntry { get; set; } - [JsonProperty(PropertyName = JsonProperties.PackageContent)] + [JsonPropertyName(JsonProperties.PackageContent)] public Uri PackageContent { get; set; } } } diff --git a/src/NuGet.Core/NuGet.Protocol/Model/RegistrationPage.cs b/src/NuGet.Core/NuGet.Protocol/Model/RegistrationPage.cs index bde0277c1ee..d08729e90dd 100644 --- a/src/NuGet.Core/NuGet.Protocol/Model/RegistrationPage.cs +++ b/src/NuGet.Core/NuGet.Protocol/Model/RegistrationPage.cs @@ -4,7 +4,7 @@ #nullable disable using System.Collections.Generic; -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace NuGet.Protocol.Model { /// @@ -15,7 +15,7 @@ namespace NuGet.Protocol.Model /// internal class RegistrationPage { - [JsonProperty("@id")] + [JsonPropertyName("@id")] public string Url { get; set; } /// @@ -23,13 +23,13 @@ internal class RegistrationPage /// the server decided not to inline the leaf items. In this case, the property can be used /// fetch another instance with the property filled in. /// - [JsonProperty("items")] + [JsonPropertyName("items")] public List Items { get; set; } - [JsonProperty("lower")] + [JsonPropertyName("lower")] public string Lower { get; set; } - [JsonProperty("upper")] + [JsonPropertyName("upper")] public string Upper { get; set; } } } diff --git a/src/NuGet.Core/NuGet.Protocol/Resources/PackageMetadataResourceV3.cs b/src/NuGet.Core/NuGet.Protocol/Resources/PackageMetadataResourceV3.cs index 3d1de4d36de..ff13b067066 100644 --- a/src/NuGet.Core/NuGet.Protocol/Resources/PackageMetadataResourceV3.cs +++ b/src/NuGet.Core/NuGet.Protocol/Resources/PackageMetadataResourceV3.cs @@ -7,11 +7,13 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using System.Text.Json; +using System.Text.Json.Serialization.Metadata; using System.Threading; using System.Threading.Tasks; -using Newtonsoft.Json; using NuGet.Common; using NuGet.Packaging.Core; +using NuGet.Protocol.Converters; using NuGet.Protocol.Core.Types; using NuGet.Protocol.Extensions; using NuGet.Protocol.Model; @@ -105,7 +107,7 @@ private async Task> GetMetadataAsync( registrationUri, packageId, sourceCacheContext, - httpSourceResult => DeserializeStreamDataAsync(httpSourceResult.Stream, token), + httpSourceResult => DeserializeStreamDataAsync(httpSourceResult.Stream, PackageMetadataJsonContext.Default.RegistrationIndex, token), log, token); @@ -158,7 +160,7 @@ private async Task> GetMetadataAsync( /// Stream data to read. /// Cancellation token. /// - private async Task DeserializeStreamDataAsync(Stream stream, CancellationToken token) + private static async Task DeserializeStreamDataAsync(Stream stream, JsonTypeInfo typeInfo, CancellationToken token) { token.ThrowIfCancellationRequested(); @@ -167,14 +169,7 @@ private async Task DeserializeStreamDataAsync(Stream stream, CancellationT return default(T); } - using (var streamReader = new StreamReader(stream)) - using (var jsonReader = new JsonTextReader(streamReader)) - { - var registrationIndex = JsonExtensions.JsonObjectSerializer - .Deserialize(jsonReader); - - return await Task.FromResult(registrationIndex); - } + return await JsonSerializer.DeserializeAsync(stream, typeInfo, token); } /// @@ -229,7 +224,7 @@ private async Task> LoadRe /// Logger Instance. /// Cancellation token. /// - private Task GetRegistratioIndexPageAsync( + private static Task GetRegistratioIndexPageAsync( HttpSource httpSource, string rangeUri, string packageId, @@ -248,7 +243,7 @@ private Task GetRegistratioIndexPageAsync( { IgnoreNotFounds = true, }, - httpSourceResult => DeserializeStreamDataAsync(httpSourceResult.Stream, token), + httpSourceResult => DeserializeStreamDataAsync(httpSourceResult.Stream, PackageMetadataJsonContext.Default.RegistrationPage, token), log, token); From cd0cff44dfb672cfc3f6649d1d054cc7e6732d0f Mon Sep 17 00:00:00 2001 From: Nigusu Yenework Date: Mon, 20 Apr 2026 12:06:13 -0700 Subject: [PATCH 3/3] disable --- .../NuGet.Protocol/Converters/PackageMetadataJsonContext.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/NuGet.Core/NuGet.Protocol/Converters/PackageMetadataJsonContext.cs b/src/NuGet.Core/NuGet.Protocol/Converters/PackageMetadataJsonContext.cs index d181652b0d4..53ba7bad04f 100644 --- a/src/NuGet.Core/NuGet.Protocol/Converters/PackageMetadataJsonContext.cs +++ b/src/NuGet.Core/NuGet.Protocol/Converters/PackageMetadataJsonContext.cs @@ -6,6 +6,7 @@ namespace NuGet.Protocol.Converters { +#pragma warning disable CS3016 // Arrays as attribute arguments is not CLS-compliant [JsonSourceGenerationOptions( PropertyNameCaseInsensitive = true, GenerationMode = JsonSourceGenerationMode.Metadata, @@ -17,6 +18,7 @@ namespace NuGet.Protocol.Converters typeof(PackageDependencyStjConverter), typeof(VersionRangeStjConverter) })] +#pragma warning restore CS3016 // Arrays as attribute arguments is not CLS-compliant [JsonSerializable(typeof(RegistrationIndex))] [JsonSerializable(typeof(RegistrationPage))] internal partial class PackageMetadataJsonContext : JsonSerializerContext