From f003645618c82e9b2c12d1f876bc37494eca0646 Mon Sep 17 00:00:00 2001 From: Jamie Magee Date: Fri, 21 Nov 2025 14:21:18 -0800 Subject: [PATCH] Add `System.Text.Json` polymorphic serialization support to `TypedComponent` This change adds support for `System.Text.Json` serialization while maintaining backward compatibility with `Newtonsoft.Json`. `TypedComponent` and its derived types can now be serialized/deserialized with either JSON library. This enables gradual migration from `Newtonsoft.Json` to `System.Text.Json` without breaking existing serialization code or changing JSON output format. Related to #231 --- .../TypedComponent/TypedComponent.cs | 32 +++++++++++++++++-- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/src/Microsoft.ComponentDetection.Contracts/TypedComponent/TypedComponent.cs b/src/Microsoft.ComponentDetection.Contracts/TypedComponent/TypedComponent.cs index 6b0489ab1..69a19ec68 100644 --- a/src/Microsoft.ComponentDetection.Contracts/TypedComponent/TypedComponent.cs +++ b/src/Microsoft.ComponentDetection.Contracts/TypedComponent/TypedComponent.cs @@ -4,18 +4,42 @@ namespace Microsoft.ComponentDetection.Contracts.TypedComponent; using System; using System.Collections.Generic; using System.Diagnostics; +using System.Text.Json.Serialization; using Microsoft.ComponentDetection.Contracts.BcdeModels; using Newtonsoft.Json; using Newtonsoft.Json.Converters; using Newtonsoft.Json.Serialization; using PackageUrl; +using JsonConverterAttribute = Newtonsoft.Json.JsonConverterAttribute; +using JsonIgnoreAttribute = Newtonsoft.Json.JsonIgnoreAttribute; [JsonObject(MemberSerialization.OptOut, NamingStrategyType = typeof(CamelCaseNamingStrategy))] [JsonConverter(typeof(TypedComponentConverter))] [DebuggerDisplay("{DebuggerDisplay,nq}")] +[JsonPolymorphic(TypeDiscriminatorPropertyName = "type")] +[JsonDerivedType(typeof(CargoComponent), typeDiscriminator: nameof(ComponentType.Cargo))] +[JsonDerivedType(typeof(ConanComponent), typeDiscriminator: nameof(ComponentType.Conan))] +[JsonDerivedType(typeof(CondaComponent), typeDiscriminator: nameof(ComponentType.Conda))] +[JsonDerivedType(typeof(DockerImageComponent), typeDiscriminator: nameof(ComponentType.DockerImage))] +[JsonDerivedType(typeof(DockerReferenceComponent), typeDiscriminator: nameof(ComponentType.DockerReference))] +[JsonDerivedType(typeof(DotNetComponent), typeDiscriminator: nameof(ComponentType.DotNet))] +[JsonDerivedType(typeof(GitComponent), typeDiscriminator: nameof(ComponentType.Git))] +[JsonDerivedType(typeof(GoComponent), typeDiscriminator: nameof(ComponentType.Go))] +[JsonDerivedType(typeof(LinuxComponent), typeDiscriminator: nameof(ComponentType.Linux))] +[JsonDerivedType(typeof(MavenComponent), typeDiscriminator: nameof(ComponentType.Maven))] +[JsonDerivedType(typeof(NpmComponent), typeDiscriminator: nameof(ComponentType.Npm))] +[JsonDerivedType(typeof(NuGetComponent), typeDiscriminator: nameof(ComponentType.NuGet))] +[JsonDerivedType(typeof(OtherComponent), typeDiscriminator: nameof(ComponentType.Other))] +[JsonDerivedType(typeof(PipComponent), typeDiscriminator: nameof(ComponentType.Pip))] +[JsonDerivedType(typeof(PodComponent), typeDiscriminator: nameof(ComponentType.Pod))] +[JsonDerivedType(typeof(RubyGemsComponent), typeDiscriminator: nameof(ComponentType.RubyGems))] +[JsonDerivedType(typeof(SpdxComponent), typeDiscriminator: nameof(ComponentType.Spdx))] +[JsonDerivedType(typeof(SwiftComponent), typeDiscriminator: nameof(ComponentType.Swift))] +[JsonDerivedType(typeof(VcpkgComponent), typeDiscriminator: nameof(ComponentType.Vcpkg))] public abstract class TypedComponent { - [JsonIgnore] + [JsonIgnore] // Newtonsoft.Json + [System.Text.Json.Serialization.JsonIgnore] // System.Text.Json private string id; internal TypedComponent() @@ -24,7 +48,8 @@ internal TypedComponent() } /// Gets the type of the component, must be well known. - [JsonConverter(typeof(StringEnumConverter))] + [JsonConverter(typeof(StringEnumConverter))] // Newtonsoft.Json + [System.Text.Json.Serialization.JsonConverter(typeof(JsonStringEnumConverter))] // System.Text.Json public abstract ComponentType Type { get; } /// Gets the id of the component. @@ -32,7 +57,8 @@ internal TypedComponent() public virtual PackageURL PackageUrl { get; } - [JsonIgnore] + [JsonIgnore] // Newtonsoft.Json + [System.Text.Json.Serialization.JsonIgnore] // System.Text.Json internal string DebuggerDisplay => $"{this.Id}"; protected string ValidateRequiredInput(string input, string fieldName, string componentType)