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/PackageMetadataJsonContext.cs b/src/NuGet.Core/NuGet.Protocol/Converters/PackageMetadataJsonContext.cs
new file mode 100644
index 00000000000..53ba7bad04f
--- /dev/null
+++ b/src/NuGet.Core/NuGet.Protocol/Converters/PackageMetadataJsonContext.cs
@@ -0,0 +1,27 @@
+// 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
+{
+#pragma warning disable CS3016 // Arrays as attribute arguments is not CLS-compliant
+ [JsonSourceGenerationOptions(
+ PropertyNameCaseInsensitive = true,
+ GenerationMode = JsonSourceGenerationMode.Metadata,
+ Converters = new[]
+ {
+ typeof(NuGetVersionStjConverter),
+ typeof(VersionInfoStjConverter),
+ typeof(PackageDependencyGroupStjConverter),
+ 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
+ {
+ }
+}
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/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);
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;
+ }
+ }
+}