diff --git a/Directory.Build.targets b/Directory.Build.targets
index 816da61d0d8..31c6cd27154 100644
--- a/Directory.Build.targets
+++ b/Directory.Build.targets
@@ -8,6 +8,11 @@
+
+
+ false
+
+
$(MSBuildWarningsAsMessages);NETSDK1138;MSB3270
diff --git a/eng/MSBuild/LegacySupport.props b/eng/MSBuild/LegacySupport.props
index 7bda63a6607..9e83541b0d8 100644
--- a/eng/MSBuild/LegacySupport.props
+++ b/eng/MSBuild/LegacySupport.props
@@ -2,7 +2,7 @@
-
+
@@ -47,10 +47,6 @@
-
-
-
-
diff --git a/src/Libraries/Microsoft.Extensions.AI.Abstractions/Microsoft.Extensions.AI.Abstractions.csproj b/src/Libraries/Microsoft.Extensions.AI.Abstractions/Microsoft.Extensions.AI.Abstractions.csproj
index 661f5d13af6..22c7461de35 100644
--- a/src/Libraries/Microsoft.Extensions.AI.Abstractions/Microsoft.Extensions.AI.Abstractions.csproj
+++ b/src/Libraries/Microsoft.Extensions.AI.Abstractions/Microsoft.Extensions.AI.Abstractions.csproj
@@ -4,6 +4,7 @@
Microsoft.Extensions.AI
Abstractions representing generative AI components.
AI
+ true
@@ -21,7 +22,6 @@
true
- true
true
true
true
@@ -29,7 +29,7 @@
true
-
+
@@ -37,5 +37,5 @@
-
+
diff --git a/src/Libraries/Microsoft.Extensions.AI.Abstractions/Utilities/AIJsonSchemaCreateContext.cs b/src/Libraries/Microsoft.Extensions.AI.Abstractions/Utilities/AIJsonSchemaCreateContext.cs
index 22e3bc6066a..5b3656630e7 100644
--- a/src/Libraries/Microsoft.Extensions.AI.Abstractions/Utilities/AIJsonSchemaCreateContext.cs
+++ b/src/Libraries/Microsoft.Extensions.AI.Abstractions/Utilities/AIJsonSchemaCreateContext.cs
@@ -15,7 +15,7 @@ namespace Microsoft.Extensions.AI;
/// Defines the context in which a JSON schema within a type graph is being generated.
///
///
-/// This struct is being passed to the user-provided
+/// This struct is being passed to the user-provided
/// callback by the method and cannot be instantiated directly.
///
public readonly struct AIJsonSchemaCreateContext
@@ -51,32 +51,20 @@ internal AIJsonSchemaCreateContext(JsonSchemaExporterContext exporterContext)
/// Gets the declaring type of the property or parameter being processed.
///
public Type? DeclaringType =>
-#if NET9_0_OR_GREATER
_exporterContext.PropertyInfo?.DeclaringType;
-#else
- _exporterContext.DeclaringType;
-#endif
///
/// Gets the corresponding to the property or field being processed.
///
public ICustomAttributeProvider? PropertyAttributeProvider =>
-#if NET9_0_OR_GREATER
_exporterContext.PropertyInfo?.AttributeProvider;
-#else
- _exporterContext.PropertyAttributeProvider;
-#endif
///
/// Gets the of the
/// constructor parameter associated with the accompanying .
///
public ICustomAttributeProvider? ParameterAttributeProvider =>
-#if NET9_0_OR_GREATER
_exporterContext.PropertyInfo?.AssociatedParameter?.AttributeProvider;
-#else
- _exporterContext.ParameterInfo;
-#endif
///
/// Retrieves a custom attribute of a specified type that is applied to the specified schema node context.
diff --git a/src/Libraries/Microsoft.Extensions.AI.Abstractions/Utilities/AIJsonUtilities.Schema.Create.cs b/src/Libraries/Microsoft.Extensions.AI.Abstractions/Utilities/AIJsonUtilities.Schema.Create.cs
index 5602ed3d5d9..667b3c4d080 100644
--- a/src/Libraries/Microsoft.Extensions.AI.Abstractions/Utilities/AIJsonUtilities.Schema.Create.cs
+++ b/src/Libraries/Microsoft.Extensions.AI.Abstractions/Utilities/AIJsonUtilities.Schema.Create.cs
@@ -199,11 +199,6 @@ internal static void ValidateSchemaDocument(JsonElement document, [CallerArgumen
}
}
-#if !NET9_0_OR_GREATER
- [UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access",
- Justification = "Pre STJ-9 schema extraction can fail with a runtime exception if certain reflection metadata have been trimmed. " +
- "The exception message will guide users to turn off 'IlcTrimMetadata' which resolves all issues.")]
-#endif
private static JsonNode CreateJsonSchemaCore(
Type? type,
ParameterInfo? parameter,
diff --git a/src/Libraries/Microsoft.Extensions.AI.AzureAIInference/Microsoft.Extensions.AI.AzureAIInference.csproj b/src/Libraries/Microsoft.Extensions.AI.AzureAIInference/Microsoft.Extensions.AI.AzureAIInference.csproj
index 3dc80561205..e40a2cc34b1 100644
--- a/src/Libraries/Microsoft.Extensions.AI.AzureAIInference/Microsoft.Extensions.AI.AzureAIInference.csproj
+++ b/src/Libraries/Microsoft.Extensions.AI.AzureAIInference/Microsoft.Extensions.AI.AzureAIInference.csproj
@@ -4,6 +4,7 @@
Microsoft.Extensions.AI
Implementation of generative AI abstractions for Azure.AI.Inference.
AI
+ true
@@ -29,9 +30,6 @@
-
-
-
diff --git a/src/Libraries/Microsoft.Extensions.AI.OpenAI/Microsoft.Extensions.AI.OpenAI.csproj b/src/Libraries/Microsoft.Extensions.AI.OpenAI/Microsoft.Extensions.AI.OpenAI.csproj
index 5c801952caa..1cad3704cbb 100644
--- a/src/Libraries/Microsoft.Extensions.AI.OpenAI/Microsoft.Extensions.AI.OpenAI.csproj
+++ b/src/Libraries/Microsoft.Extensions.AI.OpenAI/Microsoft.Extensions.AI.OpenAI.csproj
@@ -4,6 +4,7 @@
Microsoft.Extensions.AI
Implementation of generative AI abstractions for OpenAI-compatible endpoints.
AI
+ true
@@ -31,9 +32,6 @@
-
-
-
diff --git a/src/Libraries/Microsoft.Extensions.AI/ChatCompletion/ChatResponse{T}.cs b/src/Libraries/Microsoft.Extensions.AI/ChatCompletion/ChatResponse{T}.cs
index a7a6c903834..5a881397917 100644
--- a/src/Libraries/Microsoft.Extensions.AI/ChatCompletion/ChatResponse{T}.cs
+++ b/src/Libraries/Microsoft.Extensions.AI/ChatCompletion/ChatResponse{T}.cs
@@ -22,9 +22,7 @@ public class ChatResponse : ChatResponse
{
private static readonly JsonReaderOptions _allowMultipleValuesJsonReaderOptions = new()
{
-#if NET9_0_OR_GREATER
AllowMultipleValues = true
-#endif
};
private readonly JsonSerializerOptions _serializerOptions;
diff --git a/src/Libraries/Microsoft.Extensions.AI/ChatCompletion/OpenTelemetryChatClient.cs b/src/Libraries/Microsoft.Extensions.AI/ChatCompletion/OpenTelemetryChatClient.cs
index 16047a72b51..1f630a5a62c 100644
--- a/src/Libraries/Microsoft.Extensions.AI/ChatCompletion/OpenTelemetryChatClient.cs
+++ b/src/Libraries/Microsoft.Extensions.AI/ChatCompletion/OpenTelemetryChatClient.cs
@@ -69,19 +69,15 @@ public OpenTelemetryChatClient(IChatClient innerClient, ILogger? logger = null,
_tokenUsageHistogram = _meter.CreateHistogram(
OpenTelemetryConsts.GenAI.Client.TokenUsage.Name,
OpenTelemetryConsts.TokensUnit,
- OpenTelemetryConsts.GenAI.Client.TokenUsage.Description
-#if NET9_0_OR_GREATER
- , advice: new() { HistogramBucketBoundaries = OpenTelemetryConsts.GenAI.Client.TokenUsage.ExplicitBucketBoundaries }
-#endif
+ OpenTelemetryConsts.GenAI.Client.TokenUsage.Description,
+ advice: new() { HistogramBucketBoundaries = OpenTelemetryConsts.GenAI.Client.TokenUsage.ExplicitBucketBoundaries }
);
_operationDurationHistogram = _meter.CreateHistogram(
OpenTelemetryConsts.GenAI.Client.OperationDuration.Name,
OpenTelemetryConsts.SecondsUnit,
- OpenTelemetryConsts.GenAI.Client.OperationDuration.Description
-#if NET9_0_OR_GREATER
- , advice: new() { HistogramBucketBoundaries = OpenTelemetryConsts.GenAI.Client.OperationDuration.ExplicitBucketBoundaries }
-#endif
+ OpenTelemetryConsts.GenAI.Client.OperationDuration.Description,
+ advice: new() { HistogramBucketBoundaries = OpenTelemetryConsts.GenAI.Client.OperationDuration.ExplicitBucketBoundaries }
);
_jsonSerializerOptions = AIJsonUtilities.DefaultOptions;
diff --git a/src/Libraries/Microsoft.Extensions.AI/ChatCompletion/OpenTelemetryImageGenerator.cs b/src/Libraries/Microsoft.Extensions.AI/ChatCompletion/OpenTelemetryImageGenerator.cs
index 9ed2864c831..aadf5f3fed6 100644
--- a/src/Libraries/Microsoft.Extensions.AI/ChatCompletion/OpenTelemetryImageGenerator.cs
+++ b/src/Libraries/Microsoft.Extensions.AI/ChatCompletion/OpenTelemetryImageGenerator.cs
@@ -62,19 +62,15 @@ public OpenTelemetryImageGenerator(IImageGenerator innerGenerator, ILogger? logg
_tokenUsageHistogram = _meter.CreateHistogram(
OpenTelemetryConsts.GenAI.Client.TokenUsage.Name,
OpenTelemetryConsts.TokensUnit,
- OpenTelemetryConsts.GenAI.Client.TokenUsage.Description
-#if NET9_0_OR_GREATER
- , advice: new() { HistogramBucketBoundaries = OpenTelemetryConsts.GenAI.Client.TokenUsage.ExplicitBucketBoundaries }
-#endif
+ OpenTelemetryConsts.GenAI.Client.TokenUsage.Description,
+ advice: new() { HistogramBucketBoundaries = OpenTelemetryConsts.GenAI.Client.TokenUsage.ExplicitBucketBoundaries }
);
_operationDurationHistogram = _meter.CreateHistogram(
OpenTelemetryConsts.GenAI.Client.OperationDuration.Name,
OpenTelemetryConsts.SecondsUnit,
- OpenTelemetryConsts.GenAI.Client.OperationDuration.Description
-#if NET9_0_OR_GREATER
- , advice: new() { HistogramBucketBoundaries = OpenTelemetryConsts.GenAI.Client.OperationDuration.ExplicitBucketBoundaries }
-#endif
+ OpenTelemetryConsts.GenAI.Client.OperationDuration.Description,
+ advice: new() { HistogramBucketBoundaries = OpenTelemetryConsts.GenAI.Client.OperationDuration.ExplicitBucketBoundaries }
);
}
diff --git a/src/Libraries/Microsoft.Extensions.AI/Embeddings/OpenTelemetryEmbeddingGenerator.cs b/src/Libraries/Microsoft.Extensions.AI/Embeddings/OpenTelemetryEmbeddingGenerator.cs
index 9ad1d752270..f13f7273d89 100644
--- a/src/Libraries/Microsoft.Extensions.AI/Embeddings/OpenTelemetryEmbeddingGenerator.cs
+++ b/src/Libraries/Microsoft.Extensions.AI/Embeddings/OpenTelemetryEmbeddingGenerator.cs
@@ -67,19 +67,15 @@ public OpenTelemetryEmbeddingGenerator(IEmbeddingGenerator i
_tokenUsageHistogram = _meter.CreateHistogram(
OpenTelemetryConsts.GenAI.Client.TokenUsage.Name,
OpenTelemetryConsts.TokensUnit,
- OpenTelemetryConsts.GenAI.Client.TokenUsage.Description
-#if NET9_0_OR_GREATER
- , advice: new() { HistogramBucketBoundaries = OpenTelemetryConsts.GenAI.Client.TokenUsage.ExplicitBucketBoundaries }
-#endif
+ OpenTelemetryConsts.GenAI.Client.TokenUsage.Description,
+ advice: new() { HistogramBucketBoundaries = OpenTelemetryConsts.GenAI.Client.TokenUsage.ExplicitBucketBoundaries }
);
_operationDurationHistogram = _meter.CreateHistogram(
OpenTelemetryConsts.GenAI.Client.OperationDuration.Name,
OpenTelemetryConsts.SecondsUnit,
- OpenTelemetryConsts.GenAI.Client.OperationDuration.Description
-#if NET9_0_OR_GREATER
- , advice: new() { HistogramBucketBoundaries = OpenTelemetryConsts.GenAI.Client.OperationDuration.ExplicitBucketBoundaries }
-#endif
+ OpenTelemetryConsts.GenAI.Client.OperationDuration.Description,
+ advice: new() { HistogramBucketBoundaries = OpenTelemetryConsts.GenAI.Client.OperationDuration.ExplicitBucketBoundaries }
);
}
diff --git a/src/Libraries/Microsoft.Extensions.AI/Microsoft.Extensions.AI.csproj b/src/Libraries/Microsoft.Extensions.AI/Microsoft.Extensions.AI.csproj
index 54cbcc99754..960ae56de65 100644
--- a/src/Libraries/Microsoft.Extensions.AI/Microsoft.Extensions.AI.csproj
+++ b/src/Libraries/Microsoft.Extensions.AI/Microsoft.Extensions.AI.csproj
@@ -4,6 +4,7 @@
Microsoft.Extensions.AI
Utilities for working with generative AI components.
AI
+ true
@@ -24,7 +25,7 @@
at all then to spot ones that use false rather than true. Alternatively, we could try to avoid using ConfigureAwait(false)
only on paths that could lead up to the invocation of an AIFunction, but that is challenging to maintain correctly. -->
$(NoWarn);CA2007
-
+
true
true
@@ -47,7 +48,7 @@
-
+
@@ -56,5 +57,5 @@
-
+
diff --git a/src/Libraries/Microsoft.Extensions.AI/SpeechToText/OpenTelemetrySpeechToTextClient.cs b/src/Libraries/Microsoft.Extensions.AI/SpeechToText/OpenTelemetrySpeechToTextClient.cs
index f3aa90a4b21..3b0688ba585 100644
--- a/src/Libraries/Microsoft.Extensions.AI/SpeechToText/OpenTelemetrySpeechToTextClient.cs
+++ b/src/Libraries/Microsoft.Extensions.AI/SpeechToText/OpenTelemetrySpeechToTextClient.cs
@@ -64,19 +64,15 @@ public OpenTelemetrySpeechToTextClient(ISpeechToTextClient innerClient, ILogger?
_tokenUsageHistogram = _meter.CreateHistogram(
OpenTelemetryConsts.GenAI.Client.TokenUsage.Name,
OpenTelemetryConsts.TokensUnit,
- OpenTelemetryConsts.GenAI.Client.TokenUsage.Description
-#if NET9_0_OR_GREATER
- , advice: new() { HistogramBucketBoundaries = OpenTelemetryConsts.GenAI.Client.TokenUsage.ExplicitBucketBoundaries }
-#endif
+ OpenTelemetryConsts.GenAI.Client.TokenUsage.Description,
+ advice: new() { HistogramBucketBoundaries = OpenTelemetryConsts.GenAI.Client.TokenUsage.ExplicitBucketBoundaries }
);
_operationDurationHistogram = _meter.CreateHistogram(
OpenTelemetryConsts.GenAI.Client.OperationDuration.Name,
OpenTelemetryConsts.SecondsUnit,
- OpenTelemetryConsts.GenAI.Client.OperationDuration.Description
-#if NET9_0_OR_GREATER
- , advice: new() { HistogramBucketBoundaries = OpenTelemetryConsts.GenAI.Client.OperationDuration.ExplicitBucketBoundaries }
-#endif
+ OpenTelemetryConsts.GenAI.Client.OperationDuration.Description,
+ advice: new() { HistogramBucketBoundaries = OpenTelemetryConsts.GenAI.Client.OperationDuration.ExplicitBucketBoundaries }
);
}
diff --git a/src/Libraries/Microsoft.Extensions.DataIngestion.Abstractions/Microsoft.Extensions.DataIngestion.Abstractions.csproj b/src/Libraries/Microsoft.Extensions.DataIngestion.Abstractions/Microsoft.Extensions.DataIngestion.Abstractions.csproj
index 458f69c25cd..f3f16874b4c 100644
--- a/src/Libraries/Microsoft.Extensions.DataIngestion.Abstractions/Microsoft.Extensions.DataIngestion.Abstractions.csproj
+++ b/src/Libraries/Microsoft.Extensions.DataIngestion.Abstractions/Microsoft.Extensions.DataIngestion.Abstractions.csproj
@@ -6,6 +6,7 @@
Abstractions representing Data Ingestion components for RAG.
RAG
RAG;ingestion;documents
+ true
preview
false
75
diff --git a/src/Libraries/Microsoft.Extensions.DataIngestion.MarkItDown/Microsoft.Extensions.DataIngestion.MarkItDown.csproj b/src/Libraries/Microsoft.Extensions.DataIngestion.MarkItDown/Microsoft.Extensions.DataIngestion.MarkItDown.csproj
index 4699db509a8..013097ea6c7 100644
--- a/src/Libraries/Microsoft.Extensions.DataIngestion.MarkItDown/Microsoft.Extensions.DataIngestion.MarkItDown.csproj
+++ b/src/Libraries/Microsoft.Extensions.DataIngestion.MarkItDown/Microsoft.Extensions.DataIngestion.MarkItDown.csproj
@@ -6,6 +6,7 @@
Implementation of IngestionDocumentReader abstraction for MarkItDown.
RAG
RAG;ingestion;documents;markitdown
+ true
preview
false
75
diff --git a/src/Libraries/Microsoft.Extensions.DataIngestion.Markdig/Microsoft.Extensions.DataIngestion.Markdig.csproj b/src/Libraries/Microsoft.Extensions.DataIngestion.Markdig/Microsoft.Extensions.DataIngestion.Markdig.csproj
index 9e47bf144dd..5dbe9c27a34 100644
--- a/src/Libraries/Microsoft.Extensions.DataIngestion.Markdig/Microsoft.Extensions.DataIngestion.Markdig.csproj
+++ b/src/Libraries/Microsoft.Extensions.DataIngestion.Markdig/Microsoft.Extensions.DataIngestion.Markdig.csproj
@@ -6,6 +6,7 @@
Implementation of IngestionDocumentReader abstraction for Markdown.
RAG
RAG;ingestion;documents;markdown
+ true
preview
false
75
diff --git a/src/Libraries/Microsoft.Extensions.DataIngestion/Microsoft.Extensions.DataIngestion.csproj b/src/Libraries/Microsoft.Extensions.DataIngestion/Microsoft.Extensions.DataIngestion.csproj
index 7da56d2c0ea..b7515183a86 100644
--- a/src/Libraries/Microsoft.Extensions.DataIngestion/Microsoft.Extensions.DataIngestion.csproj
+++ b/src/Libraries/Microsoft.Extensions.DataIngestion/Microsoft.Extensions.DataIngestion.csproj
@@ -8,6 +8,7 @@
RAG;ingestion;documents
true
false
+ true
preview
false
75
@@ -23,16 +24,8 @@
-
-
-
-
-
-
-
-
-
+
diff --git a/src/Shared/JsonSchemaExporter/JsonSchemaExporter.JsonSchema.cs b/src/Shared/JsonSchemaExporter/JsonSchemaExporter.JsonSchema.cs
deleted file mode 100644
index 5380d208259..00000000000
--- a/src/Shared/JsonSchemaExporter/JsonSchemaExporter.JsonSchema.cs
+++ /dev/null
@@ -1,505 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-#if !NET9_0_OR_GREATER
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.Text.Json.Nodes;
-
-namespace System.Text.Json.Schema;
-
-#pragma warning disable SA1204 // Static elements should appear before instance elements
-#pragma warning disable S1144 // Unused private types or members should be removed
-
-internal static partial class JsonSchemaExporter
-{
- // Simple JSON schema representation taken from System.Text.Json
- // https://github.com/dotnet/runtime/blob/50d6cad649aad2bfa4069268eddd16fd51ec5cf3/src/libraries/System.Text.Json/src/System/Text/Json/Schema/JsonSchema.cs
- private sealed class JsonSchema
- {
- public static JsonSchema CreateFalseSchema() => new(false);
- public static JsonSchema CreateTrueSchema() => new(true);
-
- public JsonSchema()
- {
- }
-
- private JsonSchema(bool trueOrFalse)
- {
- _trueOrFalse = trueOrFalse;
- }
-
- public bool IsTrue => _trueOrFalse is true;
- public bool IsFalse => _trueOrFalse is false;
- private readonly bool? _trueOrFalse;
-
- public string? Schema
- {
- get;
- set
- {
- VerifyMutable();
- field = value;
- }
- }
-
- public string? Title
- {
- get;
- set
- {
- VerifyMutable();
- field = value;
- }
- }
-
- public string? Description
- {
- get;
- set
- {
- VerifyMutable();
- field = value;
- }
- }
-
- public string? Ref
- {
- get;
- set
- {
- VerifyMutable();
- field = value;
- }
- }
-
- public string? Comment
- {
- get;
- set
- {
- VerifyMutable();
- field = value;
- }
- }
-
- public JsonSchemaType Type
- {
- get;
- set
- {
- VerifyMutable();
- field = value;
- }
- } = JsonSchemaType.Any;
-
- public string? Format
- {
- get;
- set
- {
- VerifyMutable();
- field = value;
- }
- }
-
- public string? Pattern
- {
- get;
- set
- {
- VerifyMutable();
- field = value;
- }
- }
-
- public JsonNode? Constant
- {
- get;
- set
- {
- VerifyMutable();
- field = value;
- }
- }
-
- public List>? Properties
- {
- get;
- set
- {
- VerifyMutable();
- field = value;
- }
- }
-
- public List? Required
- {
- get;
- set
- {
- VerifyMutable();
- field = value;
- }
- }
-
- public JsonSchema? Items
- {
- get;
- set
- {
- VerifyMutable();
- field = value;
- }
- }
-
- public JsonSchema? AdditionalProperties
- {
- get;
- set
- {
- VerifyMutable();
- field = value;
- }
- }
-
- public JsonArray? Enum
- {
- get;
- set
- {
- VerifyMutable();
- field = value;
- }
- }
-
- public JsonSchema? Not
- {
- get;
- set
- {
- VerifyMutable();
- field = value;
- }
- }
-
- public List? AnyOf
- {
- get;
- set
- {
- VerifyMutable();
- field = value;
- }
- }
-
- public bool HasDefaultValue
- {
- get;
- set
- {
- VerifyMutable();
- field = value;
- }
- }
-
- public JsonNode? DefaultValue
- {
- get;
- set
- {
- VerifyMutable();
- field = value;
- }
- }
-
- public int? MinLength
- {
- get;
- set
- {
- VerifyMutable();
- field = value;
- }
- }
-
- public int? MaxLength
- {
- get;
- set
- {
- VerifyMutable();
- field = value;
- }
- }
-
- public JsonSchemaExporterContext? GenerationContext { get; set; }
-
- public int KeywordCount
- {
- get
- {
- if (_trueOrFalse != null)
- {
- return 0;
- }
-
- int count = 0;
- Count(Schema != null);
- Count(Ref != null);
- Count(Comment != null);
- Count(Title != null);
- Count(Description != null);
- Count(Type != JsonSchemaType.Any);
- Count(Format != null);
- Count(Pattern != null);
- Count(Constant != null);
- Count(Properties != null);
- Count(Required != null);
- Count(Items != null);
- Count(AdditionalProperties != null);
- Count(Enum != null);
- Count(Not != null);
- Count(AnyOf != null);
- Count(HasDefaultValue);
- Count(MinLength != null);
- Count(MaxLength != null);
-
- return count;
-
- void Count(bool isKeywordSpecified) => count += isKeywordSpecified ? 1 : 0;
- }
- }
-
- public void MakeNullable()
- {
- if (_trueOrFalse != null)
- {
- return;
- }
-
- if (Type != JsonSchemaType.Any)
- {
- Type |= JsonSchemaType.Null;
- }
- }
-
- public JsonNode ToJsonNode(JsonSchemaExporterOptions options)
- {
- if (_trueOrFalse is { } boolSchema)
- {
- return CompleteSchema((JsonNode)boolSchema);
- }
-
- var objSchema = new JsonObject();
-
- if (Schema != null)
- {
- objSchema.Add(JsonSchemaConstants.SchemaPropertyName, Schema);
- }
-
- if (Title != null)
- {
- objSchema.Add(JsonSchemaConstants.TitlePropertyName, Title);
- }
-
- if (Description != null)
- {
- objSchema.Add(JsonSchemaConstants.DescriptionPropertyName, Description);
- }
-
- if (Ref != null)
- {
- objSchema.Add(JsonSchemaConstants.RefPropertyName, Ref);
- }
-
- if (Comment != null)
- {
- objSchema.Add(JsonSchemaConstants.CommentPropertyName, Comment);
- }
-
- if (MapSchemaType(Type) is JsonNode type)
- {
- objSchema.Add(JsonSchemaConstants.TypePropertyName, type);
- }
-
- if (Format != null)
- {
- objSchema.Add(JsonSchemaConstants.FormatPropertyName, Format);
- }
-
- if (Pattern != null)
- {
- objSchema.Add(JsonSchemaConstants.PatternPropertyName, Pattern);
- }
-
- if (Constant != null)
- {
- objSchema.Add(JsonSchemaConstants.ConstPropertyName, Constant);
- }
-
- if (Properties != null)
- {
- var properties = new JsonObject();
- foreach (KeyValuePair property in Properties)
- {
- properties.Add(property.Key, property.Value.ToJsonNode(options));
- }
-
- objSchema.Add(JsonSchemaConstants.PropertiesPropertyName, properties);
- }
-
- if (Required != null)
- {
- var requiredArray = new JsonArray();
- foreach (string requiredProperty in Required)
- {
- requiredArray.Add((JsonNode)requiredProperty);
- }
-
- objSchema.Add(JsonSchemaConstants.RequiredPropertyName, requiredArray);
- }
-
- if (Items != null)
- {
- objSchema.Add(JsonSchemaConstants.ItemsPropertyName, Items.ToJsonNode(options));
- }
-
- if (AdditionalProperties != null)
- {
- objSchema.Add(JsonSchemaConstants.AdditionalPropertiesPropertyName, AdditionalProperties.ToJsonNode(options));
- }
-
- if (Enum != null)
- {
- objSchema.Add(JsonSchemaConstants.EnumPropertyName, Enum);
- }
-
- if (Not != null)
- {
- objSchema.Add(JsonSchemaConstants.NotPropertyName, Not.ToJsonNode(options));
- }
-
- if (AnyOf != null)
- {
- JsonArray anyOfArray = new();
- foreach (JsonSchema schema in AnyOf)
- {
- anyOfArray.Add(schema.ToJsonNode(options));
- }
-
- objSchema.Add(JsonSchemaConstants.AnyOfPropertyName, anyOfArray);
- }
-
- if (HasDefaultValue)
- {
- objSchema.Add(JsonSchemaConstants.DefaultPropertyName, DefaultValue);
- }
-
- if (MinLength is int minLength)
- {
- objSchema.Add(JsonSchemaConstants.MinLengthPropertyName, (JsonNode)minLength);
- }
-
- if (MaxLength is int maxLength)
- {
- objSchema.Add(JsonSchemaConstants.MaxLengthPropertyName, (JsonNode)maxLength);
- }
-
- return CompleteSchema(objSchema);
-
- JsonNode CompleteSchema(JsonNode schema)
- {
- if (GenerationContext is { } context)
- {
- Debug.Assert(options.TransformSchemaNode != null, "context should only be populated if a callback is present.");
-
- // Apply any user-defined transformations to the schema.
- return options.TransformSchemaNode!(context, schema);
- }
-
- return schema;
- }
- }
-
- public static void EnsureMutable(ref JsonSchema schema)
- {
- switch (schema._trueOrFalse)
- {
- case false:
- schema = new JsonSchema { Not = JsonSchema.CreateTrueSchema() };
- break;
- case true:
- schema = new JsonSchema();
- break;
- }
- }
-
- private static readonly JsonSchemaType[] _schemaValues = new JsonSchemaType[]
- {
- // NB the order of these values influences order of types in the rendered schema
- JsonSchemaType.String,
- JsonSchemaType.Integer,
- JsonSchemaType.Number,
- JsonSchemaType.Boolean,
- JsonSchemaType.Array,
- JsonSchemaType.Object,
- JsonSchemaType.Null,
- };
-
- private void VerifyMutable()
- {
- Debug.Assert(_trueOrFalse is null, "Schema is not mutable");
- }
-
- private static JsonNode? MapSchemaType(JsonSchemaType schemaType)
- {
- if (schemaType is JsonSchemaType.Any)
- {
- return null;
- }
-
- if (ToIdentifier(schemaType) is string identifier)
- {
- return identifier;
- }
-
- var array = new JsonArray();
- foreach (JsonSchemaType type in _schemaValues)
- {
- if ((schemaType & type) != 0)
- {
- array.Add((JsonNode)ToIdentifier(type)!);
- }
- }
-
- return array;
-
- static string? ToIdentifier(JsonSchemaType schemaType) => schemaType switch
- {
- JsonSchemaType.Null => "null",
- JsonSchemaType.Boolean => "boolean",
- JsonSchemaType.Integer => "integer",
- JsonSchemaType.Number => "number",
- JsonSchemaType.String => "string",
- JsonSchemaType.Array => "array",
- JsonSchemaType.Object => "object",
- _ => null,
- };
- }
- }
-
- [Flags]
- private enum JsonSchemaType
- {
- Any = 0, // No type declared on the schema
- Null = 1,
- Boolean = 2,
- Integer = 4,
- Number = 8,
- String = 16,
- Array = 32,
- Object = 64,
- }
-}
-#endif
diff --git a/src/Shared/JsonSchemaExporter/JsonSchemaExporter.ReflectionHelpers.cs b/src/Shared/JsonSchemaExporter/JsonSchemaExporter.ReflectionHelpers.cs
deleted file mode 100644
index 6d350dab026..00000000000
--- a/src/Shared/JsonSchemaExporter/JsonSchemaExporter.ReflectionHelpers.cs
+++ /dev/null
@@ -1,425 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-#if !NET9_0_OR_GREATER
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.Diagnostics.CodeAnalysis;
-#if !NET
-using System.Linq;
-#endif
-using System.Reflection;
-using System.Text.Json.Serialization;
-using System.Text.Json.Serialization.Metadata;
-using Microsoft.Shared.Diagnostics;
-
-#pragma warning disable S3011 // Reflection should not be used to increase accessibility of classes, methods, or fields
-
-namespace System.Text.Json.Schema;
-
-internal static partial class JsonSchemaExporter
-{
- private static class ReflectionHelpers
- {
- private const BindingFlags AllInstance = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
- private static PropertyInfo? _jsonTypeInfo_ElementType;
- private static PropertyInfo? _jsonPropertyInfo_MemberName;
- private static FieldInfo? _nullableConverter_ElementConverter_Generic;
- private static FieldInfo? _enumConverter_Options_Generic;
- private static FieldInfo? _enumConverter_NamingPolicy_Generic;
-
- public static bool IsBuiltInConverter(JsonConverter converter) =>
- converter.GetType().Assembly == typeof(JsonConverter).Assembly;
-
- public static Type GetElementType(JsonTypeInfo typeInfo)
- {
- Debug.Assert(typeInfo.Kind is JsonTypeInfoKind.Enumerable or JsonTypeInfoKind.Dictionary, "TypeInfo must be of collection type");
-
- // Uses reflection to access the element type encapsulated by a JsonTypeInfo.
- if (_jsonTypeInfo_ElementType is null)
- {
- PropertyInfo? elementTypeProperty = typeof(JsonTypeInfo).GetProperty("ElementType", AllInstance);
- _jsonTypeInfo_ElementType = Throw.IfNull(elementTypeProperty);
- }
-
- return (Type)_jsonTypeInfo_ElementType.GetValue(typeInfo)!;
- }
-
- public static string? GetMemberName(JsonPropertyInfo propertyInfo)
- {
- // Uses reflection to the member name encapsulated by a JsonPropertyInfo.
- if (_jsonPropertyInfo_MemberName is null)
- {
- PropertyInfo? memberName = typeof(JsonPropertyInfo).GetProperty("MemberName", AllInstance);
- _jsonPropertyInfo_MemberName = Throw.IfNull(memberName);
- }
-
- return (string?)_jsonPropertyInfo_MemberName.GetValue(propertyInfo);
- }
-
- public static JsonConverter GetElementConverter(JsonConverter nullableConverter)
- {
- // Uses reflection to access the element converter encapsulated by a nullable converter.
- if (_nullableConverter_ElementConverter_Generic is null)
- {
- FieldInfo? genericFieldInfo = Type
- .GetType("System.Text.Json.Serialization.Converters.NullableConverter`1, System.Text.Json")!
- .GetField("_elementConverter", AllInstance);
-
- _nullableConverter_ElementConverter_Generic = Throw.IfNull(genericFieldInfo);
- }
-
- Type converterType = nullableConverter.GetType();
- var thisFieldInfo = (FieldInfo)converterType.GetMemberWithSameMetadataDefinitionAs(_nullableConverter_ElementConverter_Generic);
- return (JsonConverter)thisFieldInfo.GetValue(nullableConverter)!;
- }
-
- public static void GetEnumConverterConfig(JsonConverter enumConverter, out JsonNamingPolicy? namingPolicy, out bool allowString)
- {
- // Uses reflection to access configuration encapsulated by an enum converter.
- if (_enumConverter_Options_Generic is null)
- {
- FieldInfo? genericFieldInfo = Type
- .GetType("System.Text.Json.Serialization.Converters.EnumConverter`1, System.Text.Json")!
- .GetField("_converterOptions", AllInstance);
-
- _enumConverter_Options_Generic = Throw.IfNull(genericFieldInfo);
- }
-
- if (_enumConverter_NamingPolicy_Generic is null)
- {
- FieldInfo? genericFieldInfo = Type
- .GetType("System.Text.Json.Serialization.Converters.EnumConverter`1, System.Text.Json")!
- .GetField("_namingPolicy", AllInstance);
-
- _enumConverter_NamingPolicy_Generic = Throw.IfNull(genericFieldInfo);
- }
-
- const int EnumConverterOptionsAllowStrings = 1;
- Type converterType = enumConverter.GetType();
- var converterOptionsField = (FieldInfo)converterType.GetMemberWithSameMetadataDefinitionAs(_enumConverter_Options_Generic);
- var namingPolicyField = (FieldInfo)converterType.GetMemberWithSameMetadataDefinitionAs(_enumConverter_NamingPolicy_Generic);
-
- namingPolicy = (JsonNamingPolicy?)namingPolicyField.GetValue(enumConverter);
- int converterOptions = (int)converterOptionsField.GetValue(enumConverter)!;
- allowString = (converterOptions & EnumConverterOptionsAllowStrings) != 0;
- }
-
- // The .NET 8 source generator doesn't populate attribute providers for properties
- // cf. https://github.com/dotnet/runtime/issues/100095
- // Work around the issue by running a query for the relevant MemberInfo using the internal MemberName property
- // https://github.com/dotnet/runtime/blob/de774ff9ee1a2c06663ab35be34b755cd8d29731/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonPropertyInfo.cs#L206
- public static ICustomAttributeProvider? ResolveAttributeProvider(
- [DynamicallyAccessedMembers(
- DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties |
- DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.NonPublicFields)]
- Type? declaringType,
- JsonPropertyInfo? propertyInfo)
- {
- if (declaringType is null || propertyInfo is null)
- {
- return null;
- }
-
- if (propertyInfo.AttributeProvider is { } provider)
- {
- return provider;
- }
-
- string? memberName = ReflectionHelpers.GetMemberName(propertyInfo);
- if (memberName is not null)
- {
- return (MemberInfo?)declaringType.GetProperty(memberName, AllInstance) ??
- declaringType.GetField(memberName, AllInstance);
- }
-
- return null;
- }
-
- // Resolves the parameters of the deserialization constructor for a type, if they exist.
- public static Func? ResolveJsonConstructorParameterMapper(
- [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)]
- Type type,
- JsonTypeInfo typeInfo)
- {
- Debug.Assert(type == typeInfo.Type, "The declaring type must match the typeInfo type.");
- Debug.Assert(typeInfo.Kind is JsonTypeInfoKind.Object, "Should only be passed object JSON kinds.");
-
- if (typeInfo.Properties.Count > 0 &&
- typeInfo.CreateObject is null && // Ensure that a default constructor isn't being used
- TryGetDeserializationConstructor(type, useDefaultCtorInAnnotatedStructs: true, out ConstructorInfo? ctor))
- {
- ParameterInfo[]? parameters = ctor?.GetParameters();
- if (parameters?.Length > 0)
- {
- Dictionary dict = new(parameters.Length);
- foreach (ParameterInfo parameter in parameters)
- {
- if (parameter.Name is not null)
- {
- // We don't care about null parameter names or conflicts since they
- // would have already been rejected by JsonTypeInfo exporterOptions.
- dict[new(parameter.Name, parameter.ParameterType)] = parameter;
- }
- }
-
- return prop => dict.TryGetValue(new(prop.Name, prop.PropertyType), out ParameterInfo? parameter) ? parameter : null;
- }
- }
-
- return null;
- }
-
- // Resolves the nullable reference type annotations for a property or field,
- // additionally addressing a few known bugs of the NullabilityInfo pre .NET 9.
- public static NullabilityInfo GetMemberNullability(NullabilityInfoContext context, MemberInfo memberInfo)
- {
- Debug.Assert(memberInfo is PropertyInfo or FieldInfo, "Member must be property or field.");
- return memberInfo is PropertyInfo prop
- ? context.Create(prop)
- : context.Create((FieldInfo)memberInfo);
- }
-
- public static NullabilityState GetParameterNullability(NullabilityInfoContext context, ParameterInfo parameterInfo)
- {
-#if NET8_0
- // Workaround for https://github.com/dotnet/runtime/issues/92487
- // The fix has been incorporated into .NET 9 (and the polyfilled implementations in netfx).
- // Should be removed once .NET 8 support is dropped.
- if (GetGenericParameterDefinition(parameterInfo) is { ParameterType: { IsGenericParameter: true } typeParam })
- {
- // Step 1. Look for nullable annotations on the type parameter.
- if (GetNullableFlags(typeParam) is byte[] flags)
- {
- return TranslateByte(flags[0]);
- }
-
- // Step 2. Look for nullable annotations on the generic method declaration.
- if (typeParam.DeclaringMethod != null && GetNullableContextFlag(typeParam.DeclaringMethod) is byte flag)
- {
- return TranslateByte(flag);
- }
-
- // Step 3. Look for nullable annotations on the generic method declaration.
- if (GetNullableContextFlag(typeParam.DeclaringType!) is byte flag2)
- {
- return TranslateByte(flag2);
- }
-
- // Default to nullable.
- return NullabilityState.Nullable;
-
- static byte[]? GetNullableFlags(MemberInfo member)
- {
- foreach (CustomAttributeData attr in member.GetCustomAttributesData())
- {
- Type attrType = attr.AttributeType;
- if (attrType.Name == "NullableAttribute" && attrType.Namespace == "System.Runtime.CompilerServices")
- {
- foreach (CustomAttributeTypedArgument ctorArg in attr.ConstructorArguments)
- {
- switch (ctorArg.Value)
- {
- case byte flag:
- return [flag];
- case byte[] flags:
- return flags;
- }
- }
- }
- }
-
- return null;
- }
-
- static byte? GetNullableContextFlag(MemberInfo member)
- {
- foreach (CustomAttributeData attr in member.GetCustomAttributesData())
- {
- Type attrType = attr.AttributeType;
- if (attrType.Name == "NullableContextAttribute" && attrType.Namespace == "System.Runtime.CompilerServices")
- {
- foreach (CustomAttributeTypedArgument ctorArg in attr.ConstructorArguments)
- {
- if (ctorArg.Value is byte flag)
- {
- return flag;
- }
- }
- }
- }
-
- return null;
- }
-
-#pragma warning disable S109 // Magic numbers should not be used
- static NullabilityState TranslateByte(byte b) => b switch
- {
- 1 => NullabilityState.NotNull,
- 2 => NullabilityState.Nullable,
- _ => NullabilityState.Unknown
- };
-#pragma warning restore S109 // Magic numbers should not be used
- }
-
- static ParameterInfo GetGenericParameterDefinition(ParameterInfo parameter)
- {
- if (parameter.Member is { DeclaringType.IsConstructedGenericType: true }
- or MethodInfo { IsGenericMethod: true, IsGenericMethodDefinition: false })
- {
- var genericMethod = (MethodBase)GetGenericMemberDefinition(parameter.Member);
- return genericMethod.GetParameters()[parameter.Position];
- }
-
- return parameter;
- }
-
- static MemberInfo GetGenericMemberDefinition(MemberInfo member)
- {
- if (member is Type type)
- {
- return type.IsConstructedGenericType ? type.GetGenericTypeDefinition() : type;
- }
-
- if (member.DeclaringType?.IsConstructedGenericType is true)
- {
- return member.DeclaringType.GetGenericTypeDefinition().GetMemberWithSameMetadataDefinitionAs(member);
- }
-
- if (member is MethodInfo { IsGenericMethod: true, IsGenericMethodDefinition: false } method)
- {
- return method.GetGenericMethodDefinition();
- }
-
- return member;
- }
-#endif
- return context.Create(parameterInfo).WriteState;
- }
-
- // Taken from https://github.com/dotnet/runtime/blob/903bc019427ca07080530751151ea636168ad334/src/libraries/System.Text.Json/Common/ReflectionExtensions.cs#L288-L317
- public static object? GetNormalizedDefaultValue(ParameterInfo parameterInfo)
- {
- Type parameterType = parameterInfo.ParameterType;
- object? defaultValue = parameterInfo.DefaultValue;
-
- if (defaultValue is null)
- {
- return null;
- }
-
- // DBNull.Value is sometimes used as the default value (returned by reflection) of nullable params in place of null.
- if (defaultValue == DBNull.Value && parameterType != typeof(DBNull))
- {
- return null;
- }
-
- // Default values of enums or nullable enums are represented using the underlying type and need to be cast explicitly
- // cf. https://github.com/dotnet/runtime/issues/68647
- if (parameterType.IsEnum)
- {
- return Enum.ToObject(parameterType, defaultValue);
- }
-
- if (Nullable.GetUnderlyingType(parameterType) is Type underlyingType && underlyingType.IsEnum)
- {
- return Enum.ToObject(underlyingType, defaultValue);
- }
-
- return defaultValue;
- }
-
- // Resolves the deserialization constructor for a type using logic copied from
- // https://github.com/dotnet/runtime/blob/e12e2fa6cbdd1f4b0c8ad1b1e2d960a480c21703/src/libraries/System.Text.Json/Common/ReflectionExtensions.cs#L227-L286
- private static bool TryGetDeserializationConstructor(
- [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)]
- Type type,
- bool useDefaultCtorInAnnotatedStructs,
- out ConstructorInfo? deserializationCtor)
- {
- ConstructorInfo? ctorWithAttribute = null;
- ConstructorInfo? publicParameterlessCtor = null;
- ConstructorInfo? lonePublicCtor = null;
-
- ConstructorInfo[] constructors = type.GetConstructors(BindingFlags.Public | BindingFlags.Instance);
-
- if (constructors.Length == 1)
- {
- lonePublicCtor = constructors[0];
- }
-
- foreach (ConstructorInfo constructor in constructors)
- {
- if (HasJsonConstructorAttribute(constructor))
- {
- if (ctorWithAttribute != null)
- {
- deserializationCtor = null;
- return false;
- }
-
- ctorWithAttribute = constructor;
- }
- else if (constructor.GetParameters().Length == 0)
- {
- publicParameterlessCtor = constructor;
- }
- }
-
- // Search for non-public ctors with [JsonConstructor].
- foreach (ConstructorInfo constructor in type.GetConstructors(BindingFlags.NonPublic | BindingFlags.Instance))
- {
- if (HasJsonConstructorAttribute(constructor))
- {
- if (ctorWithAttribute != null)
- {
- deserializationCtor = null;
- return false;
- }
-
- ctorWithAttribute = constructor;
- }
- }
-
- // Structs will use default constructor if attribute isn't used.
- if (useDefaultCtorInAnnotatedStructs && type.IsValueType && ctorWithAttribute == null)
- {
- deserializationCtor = null;
- return true;
- }
-
- deserializationCtor = ctorWithAttribute ?? publicParameterlessCtor ?? lonePublicCtor;
- return true;
-
- static bool HasJsonConstructorAttribute(ConstructorInfo constructorInfo) =>
- constructorInfo.GetCustomAttribute() != null;
- }
-
- // Parameter to property matching semantics as declared in
- // https://github.com/dotnet/runtime/blob/12d96ccfaed98e23c345188ee08f8cfe211c03e7/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfo.cs#L1007-L1030
- private readonly struct ParameterLookupKey : IEquatable
- {
- public ParameterLookupKey(string name, Type type)
- {
- Name = name;
- Type = type;
- }
-
- public string Name { get; }
- public Type Type { get; }
-
- public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Name);
- public bool Equals(ParameterLookupKey other) => Type == other.Type && string.Equals(Name, other.Name, StringComparison.OrdinalIgnoreCase);
- public override bool Equals(object? obj) => obj is ParameterLookupKey key && Equals(key);
- }
- }
-
-#if !NET
- private static MemberInfo GetMemberWithSameMetadataDefinitionAs(this Type specializedType, MemberInfo member)
- {
- const BindingFlags All = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance;
- return specializedType.GetMember(member.Name, member.MemberType, All).First(m => m.MetadataToken == member.MetadataToken);
- }
-#endif
-}
-#endif
diff --git a/src/Shared/JsonSchemaExporter/JsonSchemaExporter.cs b/src/Shared/JsonSchemaExporter/JsonSchemaExporter.cs
deleted file mode 100644
index d651ce6a727..00000000000
--- a/src/Shared/JsonSchemaExporter/JsonSchemaExporter.cs
+++ /dev/null
@@ -1,805 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-#if !NET9_0_OR_GREATER
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.Diagnostics.CodeAnalysis;
-using System.Globalization;
-using System.Linq;
-using System.Reflection;
-#if NET
-using System.Runtime.InteropServices;
-#endif
-using System.Text.Json.Nodes;
-using System.Text.Json.Serialization;
-using System.Text.Json.Serialization.Metadata;
-using Microsoft.Shared.Diagnostics;
-
-#pragma warning disable LA0002 // Use 'Microsoft.Shared.Text.NumericExtensions.ToInvariantString' for improved performance
-#pragma warning disable S107 // Methods should not have too many parameters
-#pragma warning disable S1121 // Assignments should not be made from within sub-expressions
-
-namespace System.Text.Json.Schema;
-
-///
-/// Maps .NET types to JSON schema objects using contract metadata from instances.
-///
-#if !SHARED_PROJECT
-[System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
-#endif
-internal static partial class JsonSchemaExporter
-{
- // Polyfill implementation of JsonSchemaExporter for System.Text.Json version 8.0.0.
- // Uses private reflection to access metadata not available with the older APIs of STJ.
-
- private const string RequiresUnreferencedCodeMessage =
- "Uses private reflection on System.Text.Json components to access converter metadata. " +
- "If running Native AOT ensure that the 'IlcTrimMetadata' property has been disabled.";
-
- ///
- /// Generates a JSON schema corresponding to the contract metadata of the specified type.
- ///
- /// The options instance from which to resolve the contract metadata.
- /// The root type for which to generate the JSON schema.
- /// The exporterOptions object controlling the schema generation.
- /// A new instance defining the JSON schema for .
- /// One of the specified parameters is .
- /// The parameter contains unsupported exporterOptions.
- [RequiresUnreferencedCode(RequiresUnreferencedCodeMessage)]
- public static JsonNode GetJsonSchemaAsNode(this JsonSerializerOptions options, Type type, JsonSchemaExporterOptions? exporterOptions = null)
- {
- _ = Throw.IfNull(options);
- _ = Throw.IfNull(type);
- ValidateOptions(options);
-
- exporterOptions ??= JsonSchemaExporterOptions.Default;
- JsonTypeInfo typeInfo = options.GetTypeInfo(type);
- return MapRootTypeJsonSchema(typeInfo, exporterOptions);
- }
-
- ///
- /// Generates a JSON schema corresponding to the specified contract metadata.
- ///
- /// The contract metadata for which to generate the schema.
- /// The exporterOptions object controlling the schema generation.
- /// A new instance defining the JSON schema for .
- /// One of the specified parameters is .
- /// The parameter contains unsupported exporterOptions.
- [RequiresUnreferencedCode(RequiresUnreferencedCodeMessage)]
- public static JsonNode GetJsonSchemaAsNode(this JsonTypeInfo typeInfo, JsonSchemaExporterOptions? exporterOptions = null)
- {
- _ = Throw.IfNull(typeInfo);
- ValidateOptions(typeInfo.Options);
-
- exporterOptions ??= JsonSchemaExporterOptions.Default;
- return MapRootTypeJsonSchema(typeInfo, exporterOptions);
- }
-
- [RequiresUnreferencedCode(RequiresUnreferencedCodeMessage)]
- private static JsonNode MapRootTypeJsonSchema(JsonTypeInfo typeInfo, JsonSchemaExporterOptions exporterOptions)
- {
- GenerationState state = new(exporterOptions, typeInfo.Options);
- JsonSchema schema = MapJsonSchemaCore(ref state, typeInfo);
- return schema.ToJsonNode(exporterOptions);
- }
-
- [RequiresUnreferencedCode(RequiresUnreferencedCodeMessage)]
- private static JsonSchema MapJsonSchemaCore(
- ref GenerationState state,
- JsonTypeInfo typeInfo,
- Type? parentType = null,
- JsonPropertyInfo? propertyInfo = null,
- ICustomAttributeProvider? propertyAttributeProvider = null,
- ParameterInfo? parameterInfo = null,
- bool isNonNullableType = false,
- JsonConverter? customConverter = null,
- JsonNumberHandling? customNumberHandling = null,
- JsonTypeInfo? parentPolymorphicTypeInfo = null,
- bool parentPolymorphicTypeContainsTypesWithoutDiscriminator = false,
- bool parentPolymorphicTypeIsNonNullable = false,
- KeyValuePair? typeDiscriminator = null,
- bool cacheResult = true)
- {
- Debug.Assert(typeInfo.IsReadOnly, "The specified contract must have been made read-only.");
-
- JsonSchemaExporterContext exporterContext = state.CreateContext(typeInfo, parentPolymorphicTypeInfo, parentType, propertyInfo, parameterInfo, propertyAttributeProvider);
-
- if (cacheResult && typeInfo.Kind is not JsonTypeInfoKind.None &&
- state.TryGetExistingJsonPointer(exporterContext, out string? existingJsonPointer))
- {
- // The schema context has already been generated in the schema document, return a reference to it.
- return CompleteSchema(ref state, new JsonSchema { Ref = existingJsonPointer });
- }
-
- JsonSchema schema;
- JsonConverter effectiveConverter = customConverter ?? typeInfo.Converter;
- JsonNumberHandling effectiveNumberHandling = customNumberHandling ?? typeInfo.NumberHandling ?? typeInfo.Options.NumberHandling;
-
- if (!ReflectionHelpers.IsBuiltInConverter(effectiveConverter))
- {
- // Return a `true` schema for types with user-defined converters.
- return CompleteSchema(ref state, JsonSchema.CreateTrueSchema());
- }
-
- if (parentPolymorphicTypeInfo is null && typeInfo.PolymorphismOptions is { DerivedTypes.Count: > 0 } polyOptions)
- {
- // This is the base type of a polymorphic type hierarchy. The schema for this type
- // will include an "anyOf" property with the schemas for all derived types.
-
- string typeDiscriminatorKey = polyOptions.TypeDiscriminatorPropertyName;
- List derivedTypes = polyOptions.DerivedTypes.ToList();
-
- if (!typeInfo.Type.IsAbstract && !derivedTypes.Any(derived => derived.DerivedType == typeInfo.Type))
- {
- // For non-abstract base types that haven't been explicitly configured,
- // add a trivial schema to the derived types since we should support it.
- derivedTypes.Add(new JsonDerivedType(typeInfo.Type));
- }
-
- bool containsTypesWithoutDiscriminator = derivedTypes.Exists(static derivedTypes => derivedTypes.TypeDiscriminator is null);
- JsonSchemaType schemaType = JsonSchemaType.Any;
- List? anyOf = new(derivedTypes.Count);
-
- state.PushSchemaNode(JsonSchemaConstants.AnyOfPropertyName);
-
- foreach (JsonDerivedType derivedType in derivedTypes)
- {
- Debug.Assert(derivedType.TypeDiscriminator is null or int or string, "Type discriminator does not have the expected type.");
-
- KeyValuePair? derivedTypeDiscriminator = null;
- if (derivedType.TypeDiscriminator is { } discriminatorValue)
- {
- JsonNode discriminatorNode = discriminatorValue switch
- {
- string stringId => (JsonNode)stringId,
- _ => (JsonNode)(int)discriminatorValue,
- };
-
- JsonSchema discriminatorSchema = new() { Constant = discriminatorNode };
- derivedTypeDiscriminator = new(typeDiscriminatorKey, discriminatorSchema);
- }
-
- JsonTypeInfo derivedTypeInfo = typeInfo.Options.GetTypeInfo(derivedType.DerivedType);
-
- state.PushSchemaNode(anyOf.Count.ToString(CultureInfo.InvariantCulture));
- JsonSchema derivedSchema = MapJsonSchemaCore(
- ref state,
- derivedTypeInfo,
- parentPolymorphicTypeInfo: typeInfo,
- typeDiscriminator: derivedTypeDiscriminator,
- parentPolymorphicTypeContainsTypesWithoutDiscriminator: containsTypesWithoutDiscriminator,
- parentPolymorphicTypeIsNonNullable: isNonNullableType,
- cacheResult: false);
-
- state.PopSchemaNode();
-
- // Determine if all derived schemas have the same type.
- if (anyOf.Count == 0)
- {
- schemaType = derivedSchema.Type;
- }
- else if (schemaType != derivedSchema.Type)
- {
- schemaType = JsonSchemaType.Any;
- }
-
- anyOf.Add(derivedSchema);
- }
-
- state.PopSchemaNode();
-
- if (schemaType is not JsonSchemaType.Any)
- {
- // If all derived types have the same schema type, we can simplify the schema
- // by moving the type keyword to the base schema and removing it from the derived schemas.
- foreach (JsonSchema derivedSchema in anyOf)
- {
- derivedSchema.Type = JsonSchemaType.Any;
-
- if (derivedSchema.KeywordCount == 0)
- {
- // if removing the type results in an empty schema,
- // remove the anyOf array entirely since it's always true.
- anyOf = null;
- break;
- }
- }
- }
-
- schema = new()
- {
- Type = schemaType,
- AnyOf = anyOf,
-
- // If all derived types have a discriminator, we can require it in the base schema.
- Required = containsTypesWithoutDiscriminator ? null : new() { typeDiscriminatorKey },
- };
-
- return CompleteSchema(ref state, schema);
- }
-
- if (Nullable.GetUnderlyingType(typeInfo.Type) is Type nullableElementType)
- {
- JsonTypeInfo elementTypeInfo = typeInfo.Options.GetTypeInfo(nullableElementType);
- customConverter = ExtractCustomNullableConverter(customConverter);
- schema = MapJsonSchemaCore(ref state, elementTypeInfo, customConverter: customConverter, cacheResult: false);
-
- if (schema.Enum != null)
- {
- Debug.Assert(elementTypeInfo.Type.IsEnum, "The enum keyword should only be populated by schemas for enum types.");
- schema.Enum.Add(null); // Append null to the enum array.
- }
-
- return CompleteSchema(ref state, schema);
- }
-
- switch (typeInfo.Kind)
- {
- case JsonTypeInfoKind.Object:
- List>? properties = null;
- List? required = null;
- JsonSchema? additionalProperties = null;
-
- JsonUnmappedMemberHandling effectiveUnmappedMemberHandling = typeInfo.UnmappedMemberHandling ?? typeInfo.Options.UnmappedMemberHandling;
- if (effectiveUnmappedMemberHandling is JsonUnmappedMemberHandling.Disallow)
- {
- // Disallow unspecified properties.
- additionalProperties = JsonSchema.CreateFalseSchema();
- }
-
- if (typeDiscriminator is { } typeDiscriminatorPair)
- {
- (properties = new()).Add(typeDiscriminatorPair);
- if (parentPolymorphicTypeContainsTypesWithoutDiscriminator)
- {
- // Require the discriminator here since it's not common to all derived types.
- (required = new()).Add(typeDiscriminatorPair.Key);
- }
- }
-
- Func? parameterInfoMapper =
- ReflectionHelpers.ResolveJsonConstructorParameterMapper(typeInfo.Type, typeInfo);
-
- state.PushSchemaNode(JsonSchemaConstants.PropertiesPropertyName);
- foreach (JsonPropertyInfo property in typeInfo.Properties)
- {
- if (property is { Get: null, Set: null } or { IsExtensionData: true })
- {
- continue; // Skip JsonIgnored properties and extension data
- }
-
- JsonNumberHandling? propertyNumberHandling = property.NumberHandling ?? effectiveNumberHandling;
- JsonTypeInfo propertyTypeInfo = typeInfo.Options.GetTypeInfo(property.PropertyType);
-
- // Resolve the attribute provider for the property.
- ICustomAttributeProvider? attributeProvider = ReflectionHelpers.ResolveAttributeProvider(typeInfo.Type, property);
-
- // Declare the property as nullable if either getter or setter are nullable.
- bool isNonNullableProperty = false;
- if (attributeProvider is MemberInfo memberInfo)
- {
- NullabilityInfo nullabilityInfo = ReflectionHelpers.GetMemberNullability(state.NullabilityInfoContext, memberInfo);
- isNonNullableProperty =
- (property.Get is null || nullabilityInfo.ReadState is NullabilityState.NotNull) &&
- (property.Set is null || nullabilityInfo.WriteState is NullabilityState.NotNull);
- }
-
- bool isRequired = property.IsRequired;
- bool hasDefaultValue = false;
- JsonNode? defaultValue = null;
-
- ParameterInfo? associatedParameter = parameterInfoMapper?.Invoke(property);
- if (associatedParameter != null)
- {
- ResolveParameterInfo(
- associatedParameter,
- propertyTypeInfo,
- state.NullabilityInfoContext,
- out hasDefaultValue,
- out defaultValue,
- out bool isNonNullableParameter,
- ref isRequired);
-
- isNonNullableProperty &= isNonNullableParameter;
- }
-
- state.PushSchemaNode(property.Name);
- JsonSchema propertySchema = MapJsonSchemaCore(
- ref state,
- propertyTypeInfo,
- parentType: typeInfo.Type,
- propertyInfo: property,
- parameterInfo: associatedParameter,
- propertyAttributeProvider: attributeProvider,
- isNonNullableType: isNonNullableProperty,
- customConverter: property.CustomConverter,
- customNumberHandling: propertyNumberHandling);
-
- state.PopSchemaNode();
-
- if (hasDefaultValue)
- {
- JsonSchema.EnsureMutable(ref propertySchema);
- propertySchema.DefaultValue = defaultValue;
- propertySchema.HasDefaultValue = true;
- }
-
- (properties ??= new()).Add(new(property.Name, propertySchema));
-
- if (isRequired)
- {
- (required ??= new()).Add(property.Name);
- }
- }
-
- state.PopSchemaNode();
- return CompleteSchema(ref state, new()
- {
- Type = JsonSchemaType.Object,
- Properties = properties,
- Required = required,
- AdditionalProperties = additionalProperties,
- });
-
- case JsonTypeInfoKind.Enumerable:
- Type elementType = ReflectionHelpers.GetElementType(typeInfo);
- JsonTypeInfo elementTypeInfo = typeInfo.Options.GetTypeInfo(elementType);
-
- if (typeDiscriminator is null)
- {
- state.PushSchemaNode(JsonSchemaConstants.ItemsPropertyName);
- JsonSchema items = MapJsonSchemaCore(ref state, elementTypeInfo, customNumberHandling: effectiveNumberHandling);
- state.PopSchemaNode();
-
- return CompleteSchema(ref state, new()
- {
- Type = JsonSchemaType.Array,
- Items = items.IsTrue ? null : items,
- });
- }
- else
- {
- // Polymorphic enumerable types are represented using a wrapping object:
- // { "$type" : "discriminator", "$values" : [element1, element2, ...] }
- // Which corresponds to the schema
- // { "properties" : { "$type" : { "const" : "discriminator" }, "$values" : { "type" : "array", "items" : { ... } } } }
- const string ValuesKeyword = "$values";
-
- state.PushSchemaNode(JsonSchemaConstants.PropertiesPropertyName);
- state.PushSchemaNode(ValuesKeyword);
- state.PushSchemaNode(JsonSchemaConstants.ItemsPropertyName);
-
- JsonSchema items = MapJsonSchemaCore(ref state, elementTypeInfo, customNumberHandling: effectiveNumberHandling);
-
- state.PopSchemaNode();
- state.PopSchemaNode();
- state.PopSchemaNode();
-
- return CompleteSchema(ref state, new()
- {
- Type = JsonSchemaType.Object,
- Properties = new()
- {
- typeDiscriminator.Value,
- new(ValuesKeyword,
- new JsonSchema
- {
- Type = JsonSchemaType.Array,
- Items = items.IsTrue ? null : items,
- }),
- },
- Required = parentPolymorphicTypeContainsTypesWithoutDiscriminator ? new() { typeDiscriminator.Value.Key } : null,
- });
- }
-
- case JsonTypeInfoKind.Dictionary:
- Type valueType = ReflectionHelpers.GetElementType(typeInfo);
- JsonTypeInfo valueTypeInfo = typeInfo.Options.GetTypeInfo(valueType);
-
- List>? dictProps = null;
- List? dictRequired = null;
-
- if (typeDiscriminator is { } dictDiscriminator)
- {
- dictProps = new() { dictDiscriminator };
- if (parentPolymorphicTypeContainsTypesWithoutDiscriminator)
- {
- // Require the discriminator here since it's not common to all derived types.
- dictRequired = new() { dictDiscriminator.Key };
- }
- }
-
- state.PushSchemaNode(JsonSchemaConstants.AdditionalPropertiesPropertyName);
- JsonSchema valueSchema = MapJsonSchemaCore(ref state, valueTypeInfo, customNumberHandling: effectiveNumberHandling);
- state.PopSchemaNode();
-
- return CompleteSchema(ref state, new()
- {
- Type = JsonSchemaType.Object,
- Properties = dictProps,
- Required = dictRequired,
- AdditionalProperties = valueSchema.IsTrue ? null : valueSchema,
- });
-
- default:
- Debug.Assert(typeInfo.Kind is JsonTypeInfoKind.None, "The default case should handle unrecognize type kinds.");
-
- if (_simpleTypeSchemaFactories.TryGetValue(typeInfo.Type, out Func? simpleTypeSchemaFactory))
- {
- schema = simpleTypeSchemaFactory(effectiveNumberHandling);
- }
- else if (typeInfo.Type.IsEnum)
- {
- schema = GetEnumConverterSchema(typeInfo, effectiveConverter);
- }
- else
- {
- schema = JsonSchema.CreateTrueSchema();
- }
-
- return CompleteSchema(ref state, schema);
- }
-
- JsonSchema CompleteSchema(ref GenerationState state, JsonSchema schema)
- {
- if (schema.Ref is null)
- {
- if (IsNullableSchema(ref state))
- {
- schema.MakeNullable();
- }
-
- bool IsNullableSchema(ref GenerationState state)
- {
- // A schema is marked as nullable if either:
- // 1. We have a schema for a property where either the getter or setter are marked as nullable.
- // 2. We have a schema for a Nullable type.
- // 3. We have a schema for a reference type, unless we're explicitly treating null-oblivious types as non-nullable.
-
- if (propertyInfo != null || parameterInfo != null)
- {
- return !isNonNullableType;
- }
-
- if (Nullable.GetUnderlyingType(typeInfo.Type) is not null)
- {
- return true;
- }
-
- return !typeInfo.Type.IsValueType &&
- !parentPolymorphicTypeIsNonNullable &&
- !state.ExporterOptions.TreatNullObliviousAsNonNullable;
- }
- }
-
- if (state.ExporterOptions.TransformSchemaNode != null)
- {
- // Prime the schema for invocation by the JsonNode transformer.
- schema.GenerationContext = exporterContext;
- }
-
- return schema;
- }
- }
-
- private readonly ref struct GenerationState
- {
- private const int DefaultMaxDepth = 64;
- private readonly List _currentPath = new();
- private readonly Dictionary<(JsonTypeInfo, JsonPropertyInfo?), string[]> _generated = new();
- private readonly int _maxDepth;
-
- public GenerationState(JsonSchemaExporterOptions exporterOptions, JsonSerializerOptions options, NullabilityInfoContext? nullabilityInfoContext = null)
- {
- ExporterOptions = exporterOptions;
- NullabilityInfoContext = nullabilityInfoContext ?? new();
- _maxDepth = options.MaxDepth is 0 ? DefaultMaxDepth : options.MaxDepth;
- }
-
- public JsonSchemaExporterOptions ExporterOptions { get; }
- public NullabilityInfoContext NullabilityInfoContext { get; }
- public int CurrentDepth => _currentPath.Count;
-
- public void PushSchemaNode(string nodeId)
- {
- if (CurrentDepth == _maxDepth)
- {
- ThrowHelpers.ThrowInvalidOperationException_MaxDepthReached();
- }
-
- _currentPath.Add(nodeId);
- }
-
- public void PopSchemaNode()
- {
- _currentPath.RemoveAt(_currentPath.Count - 1);
- }
-
- ///
- /// Registers the current schema node generation context; if it has already been generated return a JSON pointer to its location.
- ///
- public bool TryGetExistingJsonPointer(in JsonSchemaExporterContext context, [NotNullWhen(true)] out string? existingJsonPointer)
- {
- (JsonTypeInfo, JsonPropertyInfo?) key = (context.TypeInfo, context.PropertyInfo);
-#if NET
- ref string[]? pathToSchema = ref CollectionsMarshal.GetValueRefOrAddDefault(_generated, key, out bool exists);
-#else
- bool exists = _generated.TryGetValue(key, out string[]? pathToSchema);
-#endif
- if (exists)
- {
- existingJsonPointer = FormatJsonPointer(pathToSchema);
- return true;
- }
-#if NET
- pathToSchema = context._path;
-#else
- _generated[key] = context._path;
-#endif
- existingJsonPointer = null;
- return false;
- }
-
- public JsonSchemaExporterContext CreateContext(
- JsonTypeInfo typeInfo,
- JsonTypeInfo? baseTypeInfo,
- Type? declaringType,
- JsonPropertyInfo? propertyInfo,
- ParameterInfo? parameterInfo,
- ICustomAttributeProvider? propertyAttributeProvider)
- {
- return new JsonSchemaExporterContext(typeInfo, baseTypeInfo, declaringType, propertyInfo, parameterInfo, propertyAttributeProvider, _currentPath.ToArray());
- }
-
- private static string FormatJsonPointer(ReadOnlySpan path)
- {
- if (path.IsEmpty)
- {
- return "#";
- }
-
- StringBuilder sb = new();
- _ = sb.Append('#');
-
- for (int i = 0; i < path.Length; i++)
- {
- string segment = path[i];
- if (segment.AsSpan().IndexOfAny('~', '/') != -1)
- {
-#pragma warning disable CA1307 // Specify StringComparison for clarity
- segment = segment.Replace("~", "~0").Replace("/", "~1");
-#pragma warning restore CA1307
- }
-
- _ = sb.Append('/');
- _ = sb.Append(segment);
- }
-
- return sb.ToString();
- }
- }
-
- private static readonly Dictionary> _simpleTypeSchemaFactories = new()
- {
- [typeof(object)] = _ => JsonSchema.CreateTrueSchema(),
- [typeof(bool)] = _ => new JsonSchema { Type = JsonSchemaType.Boolean },
- [typeof(byte)] = numberHandling => GetSchemaForNumericType(JsonSchemaType.Integer, numberHandling),
- [typeof(ushort)] = numberHandling => GetSchemaForNumericType(JsonSchemaType.Integer, numberHandling),
- [typeof(uint)] = numberHandling => GetSchemaForNumericType(JsonSchemaType.Integer, numberHandling),
- [typeof(ulong)] = numberHandling => GetSchemaForNumericType(JsonSchemaType.Integer, numberHandling),
- [typeof(sbyte)] = numberHandling => GetSchemaForNumericType(JsonSchemaType.Integer, numberHandling),
- [typeof(short)] = numberHandling => GetSchemaForNumericType(JsonSchemaType.Integer, numberHandling),
- [typeof(int)] = numberHandling => GetSchemaForNumericType(JsonSchemaType.Integer, numberHandling),
- [typeof(long)] = numberHandling => GetSchemaForNumericType(JsonSchemaType.Integer, numberHandling),
- [typeof(float)] = numberHandling => GetSchemaForNumericType(JsonSchemaType.Number, numberHandling, isIeeeFloatingPoint: true),
- [typeof(double)] = numberHandling => GetSchemaForNumericType(JsonSchemaType.Number, numberHandling, isIeeeFloatingPoint: true),
- [typeof(decimal)] = numberHandling => GetSchemaForNumericType(JsonSchemaType.Number, numberHandling),
-#if NET6_0_OR_GREATER
- [typeof(Half)] = numberHandling => GetSchemaForNumericType(JsonSchemaType.Number, numberHandling, isIeeeFloatingPoint: true),
-#endif
-#if NET7_0_OR_GREATER
- [typeof(UInt128)] = numberHandling => GetSchemaForNumericType(JsonSchemaType.Integer, numberHandling),
- [typeof(Int128)] = numberHandling => GetSchemaForNumericType(JsonSchemaType.Integer, numberHandling),
-#endif
- [typeof(char)] = _ => new JsonSchema { Type = JsonSchemaType.String, MinLength = 1, MaxLength = 1 },
- [typeof(string)] = _ => new JsonSchema { Type = JsonSchemaType.String },
- [typeof(byte[])] = _ => new JsonSchema { Type = JsonSchemaType.String },
- [typeof(Memory)] = _ => new JsonSchema { Type = JsonSchemaType.String },
- [typeof(ReadOnlyMemory)] = _ => new JsonSchema { Type = JsonSchemaType.String },
- [typeof(DateTime)] = _ => new JsonSchema { Type = JsonSchemaType.String, Format = "date-time" },
- [typeof(DateTimeOffset)] = _ => new JsonSchema { Type = JsonSchemaType.String, Format = "date-time" },
- [typeof(TimeSpan)] = _ => new JsonSchema
- {
- Comment = "Represents a System.TimeSpan value.",
- Type = JsonSchemaType.String,
- Pattern = @"^-?(\d+\.)?\d{2}:\d{2}:\d{2}(\.\d{1,7})?$",
- },
-
-#if NET6_0_OR_GREATER
- [typeof(DateOnly)] = _ => new JsonSchema { Type = JsonSchemaType.String, Format = "date" },
- [typeof(TimeOnly)] = _ => new JsonSchema { Type = JsonSchemaType.String, Format = "time" },
-#endif
- [typeof(Guid)] = _ => new JsonSchema { Type = JsonSchemaType.String, Format = "uuid" },
- [typeof(Uri)] = _ => new JsonSchema { Type = JsonSchemaType.String, Format = "uri" },
- [typeof(Version)] = _ => new JsonSchema
- {
- Comment = "Represents a version string.",
- Type = JsonSchemaType.String,
- Pattern = @"^\d+(\.\d+){1,3}$",
- },
-
- [typeof(JsonDocument)] = _ => JsonSchema.CreateTrueSchema(),
- [typeof(JsonElement)] = _ => JsonSchema.CreateTrueSchema(),
- [typeof(JsonNode)] = _ => JsonSchema.CreateTrueSchema(),
- [typeof(JsonValue)] = _ => JsonSchema.CreateTrueSchema(),
- [typeof(JsonObject)] = _ => new JsonSchema { Type = JsonSchemaType.Object },
- [typeof(JsonArray)] = _ => new JsonSchema { Type = JsonSchemaType.Array },
- };
-
- // Adapted from https://github.com/dotnet/runtime/blob/release/9.0/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/JsonPrimitiveConverter.cs#L36-L69
- private static JsonSchema GetSchemaForNumericType(JsonSchemaType schemaType, JsonNumberHandling numberHandling, bool isIeeeFloatingPoint = false)
- {
- Debug.Assert(schemaType is JsonSchemaType.Integer or JsonSchemaType.Number, "schema type must be number or integer");
- Debug.Assert(!isIeeeFloatingPoint || schemaType is JsonSchemaType.Number, "If specifying IEEE the schema type must be number");
-
- string? pattern = null;
-
- if ((numberHandling & (JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.WriteAsString)) != 0)
- {
- if (schemaType is JsonSchemaType.Integer)
- {
- pattern = @"^-?(?:0|[1-9]\d*)$";
- }
- else if (isIeeeFloatingPoint)
- {
- pattern = @"^-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?$";
- }
- else
- {
- pattern = @"^-?(?:0|[1-9]\d*)(?:\.\d+)?$";
- }
-
- schemaType |= JsonSchemaType.String;
- }
-
- if (isIeeeFloatingPoint && (numberHandling & JsonNumberHandling.AllowNamedFloatingPointLiterals) != 0)
- {
- return new JsonSchema
- {
- AnyOf = new()
- {
- new JsonSchema { Type = schemaType, Pattern = pattern },
- new JsonSchema { Enum = new() { (JsonNode)"NaN", (JsonNode)"Infinity", (JsonNode)"-Infinity" } },
- },
- };
- }
-
- return new JsonSchema { Type = schemaType, Pattern = pattern };
- }
-
- private static JsonConverter? ExtractCustomNullableConverter(JsonConverter? converter)
- {
- Debug.Assert(converter is null || ReflectionHelpers.IsBuiltInConverter(converter), "If specified the converter must be built-in.");
-
- if (converter is null)
- {
- return null;
- }
-
- return ReflectionHelpers.GetElementConverter(converter);
- }
-
- private static void ValidateOptions(JsonSerializerOptions options)
- {
- if (options.ReferenceHandler == ReferenceHandler.Preserve)
- {
- ThrowHelpers.ThrowNotSupportedException_ReferenceHandlerPreserveNotSupported();
- }
-
- options.MakeReadOnly();
- }
-
- private static void ResolveParameterInfo(
- ParameterInfo parameter,
- JsonTypeInfo parameterTypeInfo,
- NullabilityInfoContext nullabilityInfoContext,
- out bool hasDefaultValue,
- out JsonNode? defaultValue,
- out bool isNonNullable,
- ref bool isRequired)
- {
- Debug.Assert(parameterTypeInfo.Type == parameter.ParameterType, "The typeInfo type must match the ParameterInfo type.");
-
- // Incorporate the nullability information from the parameter.
- isNonNullable = ReflectionHelpers.GetParameterNullability(nullabilityInfoContext, parameter) is NullabilityState.NotNull;
-
- if (parameter.HasDefaultValue)
- {
- // Append the default value to the description.
- object? defaultVal = ReflectionHelpers.GetNormalizedDefaultValue(parameter);
- defaultValue = JsonSerializer.SerializeToNode(defaultVal, parameterTypeInfo);
- hasDefaultValue = true;
- }
- else
- {
- // Parameter is not optional, mark as required.
- isRequired = true;
- defaultValue = null;
- hasDefaultValue = false;
- }
- }
-
- // Adapted from https://github.com/dotnet/runtime/blob/release/9.0/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/EnumConverter.cs#L498-L521
- private static JsonSchema GetEnumConverterSchema(JsonTypeInfo typeInfo, JsonConverter converter)
- {
- Debug.Assert(typeInfo.Type.IsEnum && ReflectionHelpers.IsBuiltInConverter(converter), "must be using a built-in enum converter.");
-
- if (converter is JsonConverterFactory factory)
- {
- converter = factory.CreateConverter(typeInfo.Type, typeInfo.Options)!;
- }
-
- ReflectionHelpers.GetEnumConverterConfig(converter, out JsonNamingPolicy? namingPolicy, out bool allowString);
-
- if (allowString)
- {
- // This explicitly ignores the integer component in converters configured as AllowNumbers | AllowStrings
- // which is the default for JsonStringEnumConverter. This sacrifices some precision in the schema for simplicity.
-
- if (typeInfo.Type.GetCustomAttribute() is not null)
- {
- // Do not report enum values in case of flags.
- return new() { Type = JsonSchemaType.String };
- }
-
- JsonArray enumValues = new();
- foreach (string name in Enum.GetNames(typeInfo.Type))
- {
- // This does not account for custom names specified via the new
- // JsonStringEnumMemberNameAttribute introduced in .NET 9.
- string effectiveName = namingPolicy?.ConvertName(name) ?? name;
- enumValues.Add((JsonNode)effectiveName);
- }
-
- return new() { Enum = enumValues };
- }
-
- return new() { Type = JsonSchemaType.Integer };
- }
-
- private static class JsonSchemaConstants
- {
- public const string SchemaPropertyName = "$schema";
- public const string RefPropertyName = "$ref";
- public const string CommentPropertyName = "$comment";
- public const string TitlePropertyName = "title";
- public const string DescriptionPropertyName = "description";
- public const string TypePropertyName = "type";
- public const string FormatPropertyName = "format";
- public const string PatternPropertyName = "pattern";
- public const string PropertiesPropertyName = "properties";
- public const string RequiredPropertyName = "required";
- public const string ItemsPropertyName = "items";
- public const string AdditionalPropertiesPropertyName = "additionalProperties";
- public const string EnumPropertyName = "enum";
- public const string NotPropertyName = "not";
- public const string AnyOfPropertyName = "anyOf";
- public const string ConstPropertyName = "const";
- public const string DefaultPropertyName = "default";
- public const string MinLengthPropertyName = "minLength";
- public const string MaxLengthPropertyName = "maxLength";
- }
-
- private static class ThrowHelpers
- {
- [DoesNotReturn]
- public static void ThrowInvalidOperationException_MaxDepthReached() =>
- throw new InvalidOperationException("The depth of the generated JSON schema exceeds the JsonSerializerOptions.MaxDepth setting.");
-
- [DoesNotReturn]
- public static void ThrowNotSupportedException_ReferenceHandlerPreserveNotSupported() =>
- throw new NotSupportedException("Schema generation not supported with ReferenceHandler.Preserve enabled.");
- }
-}
-#endif
diff --git a/src/Shared/JsonSchemaExporter/JsonSchemaExporterContext.cs b/src/Shared/JsonSchemaExporter/JsonSchemaExporterContext.cs
deleted file mode 100644
index 0be37988f8c..00000000000
--- a/src/Shared/JsonSchemaExporter/JsonSchemaExporterContext.cs
+++ /dev/null
@@ -1,76 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-#if !NET9_0_OR_GREATER
-using System.Reflection;
-using System.Text.Json.Serialization.Metadata;
-
-namespace System.Text.Json.Schema;
-
-///
-/// Defines the context in which a JSON schema within a type graph is being generated.
-///
-#if !SHARED_PROJECT
-[System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
-#endif
-internal readonly struct JsonSchemaExporterContext
-{
-#pragma warning disable IDE1006 // Naming Styles
- internal readonly string[] _path;
-#pragma warning restore IDE1006 // Naming Styles
-
- internal JsonSchemaExporterContext(
- JsonTypeInfo typeInfo,
- JsonTypeInfo? baseTypeInfo,
- Type? declaringType,
- JsonPropertyInfo? propertyInfo,
- ParameterInfo? parameterInfo,
- ICustomAttributeProvider? propertyAttributeProvider,
- string[] path)
- {
- TypeInfo = typeInfo;
- DeclaringType = declaringType;
- BaseTypeInfo = baseTypeInfo;
- PropertyInfo = propertyInfo;
- ParameterInfo = parameterInfo;
- PropertyAttributeProvider = propertyAttributeProvider;
- _path = path;
- }
-
- ///
- /// Gets the path to the schema document currently being generated.
- ///
- public ReadOnlySpan Path => _path;
-
- ///
- /// Gets the for the type being processed.
- ///
- public JsonTypeInfo TypeInfo { get; }
-
- ///
- /// Gets the declaring type of the property or parameter being processed.
- ///
- public Type? DeclaringType { get; }
-
- ///
- /// Gets the type info for the polymorphic base type if generated as a derived type.
- ///
- public JsonTypeInfo? BaseTypeInfo { get; }
-
- ///
- /// Gets the if the schema is being generated for a property.
- ///
- public JsonPropertyInfo? PropertyInfo { get; }
-
- ///
- /// Gets the if a constructor parameter
- /// has been associated with the accompanying .
- ///
- public ParameterInfo? ParameterInfo { get; }
-
- ///
- /// Gets the corresponding to the property or field being processed.
- ///
- public ICustomAttributeProvider? PropertyAttributeProvider { get; }
-}
-#endif
diff --git a/src/Shared/JsonSchemaExporter/JsonSchemaExporterOptions.cs b/src/Shared/JsonSchemaExporter/JsonSchemaExporterOptions.cs
deleted file mode 100644
index c915df0c53a..00000000000
--- a/src/Shared/JsonSchemaExporter/JsonSchemaExporterOptions.cs
+++ /dev/null
@@ -1,37 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-#if !NET9_0_OR_GREATER
-using System.Text.Json.Nodes;
-
-namespace System.Text.Json.Schema;
-
-///
-/// Controls the behavior of the class.
-///
-#if !SHARED_PROJECT
-[System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
-#endif
-internal sealed class JsonSchemaExporterOptions
-{
- ///
- /// Gets the default configuration object used by .
- ///
- public static JsonSchemaExporterOptions Default { get; } = new();
-
- ///
- /// Gets a value indicating whether non-nullable schemas should be generated for null oblivious reference types.
- ///
- ///
- /// Defaults to . Due to restrictions in the run-time representation of nullable reference types
- /// most occurrences are null oblivious and are treated as nullable by the serializer. A notable exception to that rule
- /// are nullability annotations of field, property and constructor parameters which are represented in the contract metadata.
- ///
- public bool TreatNullObliviousAsNonNullable { get; init; }
-
- ///
- /// Gets a callback that is invoked for every schema that is generated within the type graph.
- ///
- public Func? TransformSchemaNode { get; init; }
-}
-#endif
diff --git a/src/Shared/JsonSchemaExporter/NullabilityInfoContext/NullabilityInfo.cs b/src/Shared/JsonSchemaExporter/NullabilityInfoContext/NullabilityInfo.cs
deleted file mode 100644
index bd9b132cd0f..00000000000
--- a/src/Shared/JsonSchemaExporter/NullabilityInfoContext/NullabilityInfo.cs
+++ /dev/null
@@ -1,75 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-#if !NET6_0_OR_GREATER
-using System.Diagnostics.CodeAnalysis;
-
-#pragma warning disable SA1623 // Property summary documentation should match accessors
-
-namespace System.Reflection
-{
- ///
- /// A class that represents nullability info.
- ///
- [ExcludeFromCodeCoverage]
- internal sealed class NullabilityInfo
- {
- internal NullabilityInfo(Type type, NullabilityState readState, NullabilityState writeState,
- NullabilityInfo? elementType, NullabilityInfo[] typeArguments)
- {
- Type = type;
- ReadState = readState;
- WriteState = writeState;
- ElementType = elementType;
- GenericTypeArguments = typeArguments;
- }
-
- ///
- /// The of the member or generic parameter
- /// to which this NullabilityInfo belongs.
- ///
- public Type Type { get; }
-
- ///
- /// The nullability read state of the member.
- ///
- public NullabilityState ReadState { get; internal set; }
-
- ///
- /// The nullability write state of the member.
- ///
- public NullabilityState WriteState { get; internal set; }
-
- ///
- /// If the member type is an array, gives the of the elements of the array, null otherwise.
- ///
- public NullabilityInfo? ElementType { get; }
-
- ///
- /// If the member type is a generic type, gives the array of for each type parameter.
- ///
- public NullabilityInfo[] GenericTypeArguments { get; }
- }
-
- ///
- /// An enum that represents nullability state.
- ///
- internal enum NullabilityState
- {
- ///
- /// Nullability context not enabled (oblivious).
- ///
- Unknown,
-
- ///
- /// Non nullable value or reference type.
- ///
- NotNull,
-
- ///
- /// Nullable value or reference type.
- ///
- Nullable,
- }
-}
-#endif
diff --git a/src/Shared/JsonSchemaExporter/NullabilityInfoContext/NullabilityInfoContext.cs b/src/Shared/JsonSchemaExporter/NullabilityInfoContext/NullabilityInfoContext.cs
deleted file mode 100644
index 3edee1b9cb8..00000000000
--- a/src/Shared/JsonSchemaExporter/NullabilityInfoContext/NullabilityInfoContext.cs
+++ /dev/null
@@ -1,661 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-#if !NET6_0_OR_GREATER
-using System.Collections.Generic;
-using System.Collections.ObjectModel;
-using System.Diagnostics;
-using System.Diagnostics.CodeAnalysis;
-using System.Linq;
-
-#pragma warning disable SA1204 // Static elements should appear before instance elements
-#pragma warning disable S109 // Magic numbers should not be used
-#pragma warning disable S1067 // Expressions should not be too complex
-#pragma warning disable S4136 // Method overloads should be grouped together
-#pragma warning disable SA1202 // Elements should be ordered by access
-#pragma warning disable IDE1006 // Naming Styles
-
-namespace System.Reflection
-{
- ///
- /// Provides APIs for populating nullability information/context from reflection members:
- /// , , and .
- ///
- [ExcludeFromCodeCoverage]
- internal sealed class NullabilityInfoContext
- {
- private const string CompilerServicesNameSpace = "System.Runtime.CompilerServices";
- private readonly Dictionary _publicOnlyModules = new();
- private readonly Dictionary _context = new();
-
- [Flags]
- private enum NotAnnotatedStatus
- {
- None = 0x0, // no restriction, all members annotated
- Private = 0x1, // private members not annotated
- Internal = 0x2, // internal members not annotated
- }
-
- private NullabilityState? GetNullableContext(MemberInfo? memberInfo)
- {
- while (memberInfo != null)
- {
- if (_context.TryGetValue(memberInfo, out NullabilityState state))
- {
- return state;
- }
-
- foreach (CustomAttributeData attribute in memberInfo.GetCustomAttributesData())
- {
- if (attribute.AttributeType.Name == "NullableContextAttribute" &&
- attribute.AttributeType.Namespace == CompilerServicesNameSpace &&
- attribute.ConstructorArguments.Count == 1)
- {
- state = TranslateByte(attribute.ConstructorArguments[0].Value);
- _context.Add(memberInfo, state);
- return state;
- }
- }
-
- memberInfo = memberInfo.DeclaringType;
- }
-
- return null;
- }
-
- ///
- /// Populates for the given .
- /// If the nullablePublicOnly feature is set for an assembly, like it does in .NET SDK, the private and/or internal member's
- /// nullability attributes are omitted, in this case the API will return NullabilityState.Unknown state.
- ///
- /// The parameter which nullability info gets populated.
- /// If the parameterInfo parameter is null.
- /// .
- public NullabilityInfo Create(ParameterInfo parameterInfo)
- {
- IList attributes = parameterInfo.GetCustomAttributesData();
- NullableAttributeStateParser parser = parameterInfo.Member is MethodBase method && IsPrivateOrInternalMethodAndAnnotationDisabled(method)
- ? NullableAttributeStateParser.Unknown
- : CreateParser(attributes);
- NullabilityInfo nullability = GetNullabilityInfo(parameterInfo.Member, parameterInfo.ParameterType, parser);
-
- if (nullability.ReadState != NullabilityState.Unknown)
- {
- CheckParameterMetadataType(parameterInfo, nullability);
- }
-
- CheckNullabilityAttributes(nullability, attributes);
- return nullability;
- }
-
- private void CheckParameterMetadataType(ParameterInfo parameter, NullabilityInfo nullability)
- {
- ParameterInfo? metaParameter;
- MemberInfo metaMember;
-
- switch (parameter.Member)
- {
- case ConstructorInfo ctor:
- var metaCtor = (ConstructorInfo)GetMemberMetadataDefinition(ctor);
- metaMember = metaCtor;
- metaParameter = GetMetaParameter(metaCtor, parameter);
- break;
-
- case MethodInfo method:
- MethodInfo metaMethod = GetMethodMetadataDefinition(method);
- metaMember = metaMethod;
- metaParameter = string.IsNullOrEmpty(parameter.Name) ? metaMethod.ReturnParameter : GetMetaParameter(metaMethod, parameter);
- break;
-
- default:
- return;
- }
-
- if (metaParameter != null)
- {
- CheckGenericParameters(nullability, metaMember, metaParameter.ParameterType, parameter.Member.ReflectedType);
- }
- }
-
- private static ParameterInfo? GetMetaParameter(MethodBase metaMethod, ParameterInfo parameter)
- {
- var parameters = metaMethod.GetParameters();
- for (int i = 0; i < parameters.Length; i++)
- {
- if (parameter.Position == i &&
- parameter.Name == parameters[i].Name)
- {
- return parameters[i];
- }
- }
-
- return null;
- }
-
- private static MethodInfo GetMethodMetadataDefinition(MethodInfo method)
- {
- if (method.IsGenericMethod && !method.IsGenericMethodDefinition)
- {
- method = method.GetGenericMethodDefinition();
- }
-
- return (MethodInfo)GetMemberMetadataDefinition(method);
- }
-
- private static void CheckNullabilityAttributes(NullabilityInfo nullability, IList attributes)
- {
- var codeAnalysisReadState = NullabilityState.Unknown;
- var codeAnalysisWriteState = NullabilityState.Unknown;
-
- foreach (CustomAttributeData attribute in attributes)
- {
- if (attribute.AttributeType.Namespace == "System.Diagnostics.CodeAnalysis")
- {
- if (attribute.AttributeType.Name == "NotNullAttribute")
- {
- codeAnalysisReadState = NullabilityState.NotNull;
- }
- else if ((attribute.AttributeType.Name == "MaybeNullAttribute" ||
- attribute.AttributeType.Name == "MaybeNullWhenAttribute") &&
- codeAnalysisReadState == NullabilityState.Unknown &&
- !IsValueTypeOrValueTypeByRef(nullability.Type))
- {
- codeAnalysisReadState = NullabilityState.Nullable;
- }
- else if (attribute.AttributeType.Name == "DisallowNullAttribute")
- {
- codeAnalysisWriteState = NullabilityState.NotNull;
- }
- else if (attribute.AttributeType.Name == "AllowNullAttribute" &&
- codeAnalysisWriteState == NullabilityState.Unknown &&
- !IsValueTypeOrValueTypeByRef(nullability.Type))
- {
- codeAnalysisWriteState = NullabilityState.Nullable;
- }
- }
- }
-
- if (codeAnalysisReadState != NullabilityState.Unknown)
- {
- nullability.ReadState = codeAnalysisReadState;
- }
-
- if (codeAnalysisWriteState != NullabilityState.Unknown)
- {
- nullability.WriteState = codeAnalysisWriteState;
- }
- }
-
- ///
- /// Populates for the given .
- /// If the nullablePublicOnly feature is set for an assembly, like it does in .NET SDK, the private and/or internal member's
- /// nullability attributes are omitted, in this case the API will return NullabilityState.Unknown state.
- ///
- /// The parameter which nullability info gets populated.
- /// If the propertyInfo parameter is null.
- /// .
- public NullabilityInfo Create(PropertyInfo propertyInfo)
- {
- MethodInfo? getter = propertyInfo.GetGetMethod(true);
- MethodInfo? setter = propertyInfo.GetSetMethod(true);
- bool annotationsDisabled = (getter == null || IsPrivateOrInternalMethodAndAnnotationDisabled(getter))
- && (setter == null || IsPrivateOrInternalMethodAndAnnotationDisabled(setter));
- NullableAttributeStateParser parser = annotationsDisabled ? NullableAttributeStateParser.Unknown : CreateParser(propertyInfo.GetCustomAttributesData());
- NullabilityInfo nullability = GetNullabilityInfo(propertyInfo, propertyInfo.PropertyType, parser);
-
- if (getter != null)
- {
- CheckNullabilityAttributes(nullability, getter.ReturnParameter.GetCustomAttributesData());
- }
- else
- {
- nullability.ReadState = NullabilityState.Unknown;
- }
-
- if (setter != null)
- {
- CheckNullabilityAttributes(nullability, setter.GetParameters().Last().GetCustomAttributesData());
- }
- else
- {
- nullability.WriteState = NullabilityState.Unknown;
- }
-
- return nullability;
- }
-
- private bool IsPrivateOrInternalMethodAndAnnotationDisabled(MethodBase method)
- {
- if ((method.IsPrivate || method.IsFamilyAndAssembly || method.IsAssembly) &&
- IsPublicOnly(method.IsPrivate, method.IsFamilyAndAssembly, method.IsAssembly, method.Module))
- {
- return true;
- }
-
- return false;
- }
-
- ///
- /// Populates for the given .
- /// If the nullablePublicOnly feature is set for an assembly, like it does in .NET SDK, the private and/or internal member's
- /// nullability attributes are omitted, in this case the API will return NullabilityState.Unknown state.
- ///
- /// The parameter which nullability info gets populated.
- /// If the eventInfo parameter is null.
- /// .
- public NullabilityInfo Create(EventInfo eventInfo)
- {
- return GetNullabilityInfo(eventInfo, eventInfo.EventHandlerType!, CreateParser(eventInfo.GetCustomAttributesData()));
- }
-
- ///
- /// Populates for the given
- /// If the nullablePublicOnly feature is set for an assembly, like it does in .NET SDK, the private and/or internal member's
- /// nullability attributes are omitted, in this case the API will return NullabilityState.Unknown state.
- ///
- /// The parameter which nullability info gets populated.
- /// If the fieldInfo parameter is null.
- /// .
- public NullabilityInfo Create(FieldInfo fieldInfo)
- {
- IList attributes = fieldInfo.GetCustomAttributesData();
- NullableAttributeStateParser parser = IsPrivateOrInternalFieldAndAnnotationDisabled(fieldInfo) ? NullableAttributeStateParser.Unknown : CreateParser(attributes);
- NullabilityInfo nullability = GetNullabilityInfo(fieldInfo, fieldInfo.FieldType, parser);
- CheckNullabilityAttributes(nullability, attributes);
- return nullability;
- }
-
- private bool IsPrivateOrInternalFieldAndAnnotationDisabled(FieldInfo fieldInfo)
- {
- if ((fieldInfo.IsPrivate || fieldInfo.IsFamilyAndAssembly || fieldInfo.IsAssembly) &&
- IsPublicOnly(fieldInfo.IsPrivate, fieldInfo.IsFamilyAndAssembly, fieldInfo.IsAssembly, fieldInfo.Module))
- {
- return true;
- }
-
- return false;
- }
-
- private bool IsPublicOnly(bool isPrivate, bool isFamilyAndAssembly, bool isAssembly, Module module)
- {
- if (!_publicOnlyModules.TryGetValue(module, out NotAnnotatedStatus value))
- {
- value = PopulateAnnotationInfo(module.GetCustomAttributesData());
- _publicOnlyModules.Add(module, value);
- }
-
- if (value == NotAnnotatedStatus.None)
- {
- return false;
- }
-
- if (((isPrivate || isFamilyAndAssembly) && value.HasFlag(NotAnnotatedStatus.Private)) ||
- (isAssembly && value.HasFlag(NotAnnotatedStatus.Internal)))
- {
- return true;
- }
-
- return false;
- }
-
- private static NotAnnotatedStatus PopulateAnnotationInfo(IList customAttributes)
- {
- foreach (CustomAttributeData attribute in customAttributes)
- {
- if (attribute.AttributeType.Name == "NullablePublicOnlyAttribute" &&
- attribute.AttributeType.Namespace == CompilerServicesNameSpace &&
- attribute.ConstructorArguments.Count == 1)
- {
- if (attribute.ConstructorArguments[0].Value is bool boolValue && boolValue)
- {
- return NotAnnotatedStatus.Internal | NotAnnotatedStatus.Private;
- }
- else
- {
- return NotAnnotatedStatus.Private;
- }
- }
- }
-
- return NotAnnotatedStatus.None;
- }
-
- private NullabilityInfo GetNullabilityInfo(MemberInfo memberInfo, Type type, NullableAttributeStateParser parser)
- {
- int index = 0;
- NullabilityInfo nullability = GetNullabilityInfo(memberInfo, type, parser, ref index);
-
- if (nullability.ReadState != NullabilityState.Unknown)
- {
- TryLoadGenericMetaTypeNullability(memberInfo, nullability);
- }
-
- return nullability;
- }
-
- private NullabilityInfo GetNullabilityInfo(MemberInfo memberInfo, Type type, NullableAttributeStateParser parser, ref int index)
- {
- NullabilityState state = NullabilityState.Unknown;
- NullabilityInfo? elementState = null;
- NullabilityInfo[] genericArgumentsState = Array.Empty();
- Type underlyingType = type;
-
- if (underlyingType.IsByRef || underlyingType.IsPointer)
- {
- underlyingType = underlyingType.GetElementType()!;
- }
-
- if (underlyingType.IsValueType)
- {
- if (Nullable.GetUnderlyingType(underlyingType) is { } nullableUnderlyingType)
- {
- underlyingType = nullableUnderlyingType;
- state = NullabilityState.Nullable;
- }
- else
- {
- state = NullabilityState.NotNull;
- }
-
- if (underlyingType.IsGenericType)
- {
- ++index;
- }
- }
- else
- {
- if (!parser.ParseNullableState(index++, ref state)
- && GetNullableContext(memberInfo) is { } contextState)
- {
- state = contextState;
- }
-
- if (underlyingType.IsArray)
- {
- elementState = GetNullabilityInfo(memberInfo, underlyingType.GetElementType()!, parser, ref index);
- }
- }
-
- if (underlyingType.IsGenericType)
- {
- Type[] genericArguments = underlyingType.GetGenericArguments();
- genericArgumentsState = new NullabilityInfo[genericArguments.Length];
-
- for (int i = 0; i < genericArguments.Length; i++)
- {
- genericArgumentsState[i] = GetNullabilityInfo(memberInfo, genericArguments[i], parser, ref index);
- }
- }
-
- return new NullabilityInfo(type, state, state, elementState, genericArgumentsState);
- }
-
- private static NullableAttributeStateParser CreateParser(IList customAttributes)
- {
- foreach (CustomAttributeData attribute in customAttributes)
- {
- if (attribute.AttributeType.Name == "NullableAttribute" &&
- attribute.AttributeType.Namespace == CompilerServicesNameSpace &&
- attribute.ConstructorArguments.Count == 1)
- {
- return new NullableAttributeStateParser(attribute.ConstructorArguments[0].Value);
- }
- }
-
- return new NullableAttributeStateParser(null);
- }
-
- private void TryLoadGenericMetaTypeNullability(MemberInfo memberInfo, NullabilityInfo nullability)
- {
- MemberInfo? metaMember = GetMemberMetadataDefinition(memberInfo);
- Type? metaType = null;
- if (metaMember is FieldInfo field)
- {
- metaType = field.FieldType;
- }
- else if (metaMember is PropertyInfo property)
- {
- metaType = GetPropertyMetaType(property);
- }
-
- if (metaType != null)
- {
- CheckGenericParameters(nullability, metaMember!, metaType, memberInfo.ReflectedType);
- }
- }
-
- private static MemberInfo GetMemberMetadataDefinition(MemberInfo member)
- {
- Type? type = member.DeclaringType;
- if ((type != null) && type.IsGenericType && !type.IsGenericTypeDefinition)
- {
- return NullabilityInfoHelpers.GetMemberWithSameMetadataDefinitionAs(type.GetGenericTypeDefinition(), member);
- }
-
- return member;
- }
-
- private static Type GetPropertyMetaType(PropertyInfo property)
- {
- if (property.GetGetMethod(true) is MethodInfo method)
- {
- return method.ReturnType;
- }
-
- return property.GetSetMethod(true)!.GetParameters()[0].ParameterType;
- }
-
- private void CheckGenericParameters(NullabilityInfo nullability, MemberInfo metaMember, Type metaType, Type? reflectedType)
- {
- if (metaType.IsGenericParameter)
- {
- if (nullability.ReadState == NullabilityState.NotNull)
- {
- _ = TryUpdateGenericParameterNullability(nullability, metaType, reflectedType);
- }
- }
- else if (metaType.ContainsGenericParameters)
- {
- if (nullability.GenericTypeArguments.Length > 0)
- {
- Type[] genericArguments = metaType.GetGenericArguments();
-
- for (int i = 0; i < genericArguments.Length; i++)
- {
- CheckGenericParameters(nullability.GenericTypeArguments[i], metaMember, genericArguments[i], reflectedType);
- }
- }
- else if (nullability.ElementType is { } elementNullability && metaType.IsArray)
- {
- CheckGenericParameters(elementNullability, metaMember, metaType.GetElementType()!, reflectedType);
- }
-
- // We could also follow this branch for metaType.IsPointer, but since pointers must be unmanaged this
- // will be a no-op regardless
- else if (metaType.IsByRef)
- {
- CheckGenericParameters(nullability, metaMember, metaType.GetElementType()!, reflectedType);
- }
- }
- }
-
- private bool TryUpdateGenericParameterNullability(NullabilityInfo nullability, Type genericParameter, Type? reflectedType)
- {
- Debug.Assert(genericParameter.IsGenericParameter, "must be generic parameter");
-
- if (reflectedType is not null
- && !genericParameter.IsGenericMethodParameter()
- && TryUpdateGenericTypeParameterNullabilityFromReflectedType(nullability, genericParameter, reflectedType, reflectedType))
- {
- return true;
- }
-
- if (IsValueTypeOrValueTypeByRef(nullability.Type))
- {
- return true;
- }
-
- var state = NullabilityState.Unknown;
- if (CreateParser(genericParameter.GetCustomAttributesData()).ParseNullableState(0, ref state))
- {
- nullability.ReadState = state;
- nullability.WriteState = state;
- return true;
- }
-
- if (GetNullableContext(genericParameter) is { } contextState)
- {
- nullability.ReadState = contextState;
- nullability.WriteState = contextState;
- return true;
- }
-
- return false;
- }
-
- private bool TryUpdateGenericTypeParameterNullabilityFromReflectedType(NullabilityInfo nullability, Type genericParameter, Type context, Type reflectedType)
- {
- Debug.Assert(genericParameter.IsGenericParameter && !genericParameter.IsGenericMethodParameter(), "must be generic parameter");
-
- Type contextTypeDefinition = context.IsGenericType && !context.IsGenericTypeDefinition ? context.GetGenericTypeDefinition() : context;
- if (genericParameter.DeclaringType == contextTypeDefinition)
- {
- return false;
- }
-
- Type? baseType = contextTypeDefinition.BaseType;
- if (baseType is null)
- {
- return false;
- }
-
- if (!baseType.IsGenericType
- || (baseType.IsGenericTypeDefinition ? baseType : baseType.GetGenericTypeDefinition()) != genericParameter.DeclaringType)
- {
- return TryUpdateGenericTypeParameterNullabilityFromReflectedType(nullability, genericParameter, baseType, reflectedType);
- }
-
- Type[] genericArguments = baseType.GetGenericArguments();
- Type genericArgument = genericArguments[genericParameter.GenericParameterPosition];
- if (genericArgument.IsGenericParameter)
- {
- return TryUpdateGenericParameterNullability(nullability, genericArgument, reflectedType);
- }
-
- NullableAttributeStateParser parser = CreateParser(contextTypeDefinition.GetCustomAttributesData());
- int nullabilityStateIndex = 1; // start at 1 since index 0 is the type itself
- for (int i = 0; i < genericParameter.GenericParameterPosition; i++)
- {
- nullabilityStateIndex += CountNullabilityStates(genericArguments[i]);
- }
-
- return TryPopulateNullabilityInfo(nullability, parser, ref nullabilityStateIndex);
-
- static int CountNullabilityStates(Type type)
- {
- Type underlyingType = Nullable.GetUnderlyingType(type) ?? type;
- if (underlyingType.IsGenericType)
- {
- int count = 1;
- foreach (Type genericArgument in underlyingType.GetGenericArguments())
- {
- count += CountNullabilityStates(genericArgument);
- }
-
- return count;
- }
-
- if (underlyingType.HasElementType)
- {
- return (underlyingType.IsArray ? 1 : 0) + CountNullabilityStates(underlyingType.GetElementType()!);
- }
-
- return type.IsValueType ? 0 : 1;
- }
- }
-
-#pragma warning disable SA1204 // Static elements should appear before instance elements
- private static bool TryPopulateNullabilityInfo(NullabilityInfo nullability, NullableAttributeStateParser parser, ref int index)
-#pragma warning restore SA1204 // Static elements should appear before instance elements
- {
- bool isValueType = IsValueTypeOrValueTypeByRef(nullability.Type);
- if (!isValueType)
- {
- var state = NullabilityState.Unknown;
- if (!parser.ParseNullableState(index, ref state))
- {
- return false;
- }
-
- nullability.ReadState = state;
- nullability.WriteState = state;
- }
-
- if (!isValueType || (Nullable.GetUnderlyingType(nullability.Type) ?? nullability.Type).IsGenericType)
- {
- index++;
- }
-
- if (nullability.GenericTypeArguments.Length > 0)
- {
- foreach (NullabilityInfo genericTypeArgumentNullability in nullability.GenericTypeArguments)
- {
- _ = TryPopulateNullabilityInfo(genericTypeArgumentNullability, parser, ref index);
- }
- }
- else if (nullability.ElementType is { } elementTypeNullability)
- {
- _ = TryPopulateNullabilityInfo(elementTypeNullability, parser, ref index);
- }
-
- return true;
- }
-
- private static NullabilityState TranslateByte(object? value)
- {
- return value is byte b ? TranslateByte(b) : NullabilityState.Unknown;
- }
-
- private static NullabilityState TranslateByte(byte b) =>
- b switch
- {
- 1 => NullabilityState.NotNull,
- 2 => NullabilityState.Nullable,
- _ => NullabilityState.Unknown
- };
-
- private static bool IsValueTypeOrValueTypeByRef(Type type) =>
- type.IsValueType || ((type.IsByRef || type.IsPointer) && type.GetElementType()!.IsValueType);
-
- private readonly struct NullableAttributeStateParser
- {
- private static readonly object UnknownByte = (byte)0;
-
- private readonly object? _nullableAttributeArgument;
-
- public NullableAttributeStateParser(object? nullableAttributeArgument)
- {
- _nullableAttributeArgument = nullableAttributeArgument;
- }
-
- public static NullableAttributeStateParser Unknown => new(UnknownByte);
-
- public bool ParseNullableState(int index, ref NullabilityState state)
- {
- switch (_nullableAttributeArgument)
- {
- case byte b:
- state = TranslateByte(b);
- return true;
- case ReadOnlyCollection args
- when index < args.Count && args[index].Value is byte elementB:
- state = TranslateByte(elementB);
- return true;
- default:
- return false;
- }
- }
- }
- }
-}
-#endif
diff --git a/src/Shared/JsonSchemaExporter/NullabilityInfoContext/NullabilityInfoHelpers.cs b/src/Shared/JsonSchemaExporter/NullabilityInfoContext/NullabilityInfoHelpers.cs
deleted file mode 100644
index 1ee573a0020..00000000000
--- a/src/Shared/JsonSchemaExporter/NullabilityInfoContext/NullabilityInfoHelpers.cs
+++ /dev/null
@@ -1,47 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-#if !NET6_0_OR_GREATER
-using System.Diagnostics.CodeAnalysis;
-
-#pragma warning disable IDE1006 // Naming Styles
-#pragma warning disable S3011 // Reflection should not be used to increase accessibility of classes, methods, or fields
-
-namespace System.Reflection
-{
- ///
- /// Polyfills for System.Private.CoreLib internals.
- ///
- [ExcludeFromCodeCoverage]
- internal static class NullabilityInfoHelpers
- {
- public static MemberInfo GetMemberWithSameMetadataDefinitionAs(Type type, MemberInfo member)
- {
- const BindingFlags all = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance;
- foreach (var info in type.GetMembers(all))
- {
- if (info.HasSameMetadataDefinitionAs(member))
- {
- return info;
- }
- }
-
- throw new MissingMemberException(type.FullName, member.Name);
- }
-
- // https://github.com/dotnet/runtime/blob/main/src/coreclr/System.Private.CoreLib/src/System/Reflection/MemberInfo.Internal.cs
- public static bool HasSameMetadataDefinitionAs(this MemberInfo target, MemberInfo other)
- {
- return target.MetadataToken == other.MetadataToken &&
- target.Module.Equals(other.Module);
- }
-
- // https://github.com/dotnet/runtime/issues/23493
- public static bool IsGenericMethodParameter(this Type target)
- {
- return target.IsGenericParameter &&
- target.DeclaringMethod != null;
- }
- }
-}
-#endif
diff --git a/src/Shared/JsonSchemaExporter/README.md b/src/Shared/JsonSchemaExporter/README.md
deleted file mode 100644
index 1a4d13c5841..00000000000
--- a/src/Shared/JsonSchemaExporter/README.md
+++ /dev/null
@@ -1,11 +0,0 @@
-# JsonSchemaExporter
-
-Provides a polyfill for the [.NET 9 `JsonSchemaExporter` component](https://learn.microsoft.com/dotnet/standard/serialization/system-text-json/extract-schema) that is compatible with all supported targets using System.Text.Json version 8.
-
-To use this in your project, add the following to your `.csproj` file:
-
-```xml
-
- true
-
-```
diff --git a/test/Libraries/Microsoft.Extensions.AI.Abstractions.Tests/Utilities/AIJsonUtilitiesTests.cs b/test/Libraries/Microsoft.Extensions.AI.Abstractions.Tests/Utilities/AIJsonUtilitiesTests.cs
index eb81dd90ecb..8ebc20b957e 100644
--- a/test/Libraries/Microsoft.Extensions.AI.Abstractions.Tests/Utilities/AIJsonUtilitiesTests.cs
+++ b/test/Libraries/Microsoft.Extensions.AI.Abstractions.Tests/Utilities/AIJsonUtilitiesTests.cs
@@ -362,9 +362,7 @@ public static void CreateFunctionJsonSchema_TreatsIntegralTypesAsInteger_EvenWit
JsonElement schemaParameters = func.JsonSchema.GetProperty("properties");
Assert.NotNull(func.UnderlyingMethod);
ParameterInfo[] parameters = func.UnderlyingMethod.GetParameters();
-#if NET9_0_OR_GREATER
Assert.Equal(parameters.Length, schemaParameters.GetPropertyCount());
-#endif
int i = 0;
foreach (JsonProperty property in schemaParameters.EnumerateObject())
@@ -1434,13 +1432,7 @@ private partial class JsonContext : JsonSerializerContext;
private static bool DeepEquals(JsonElement element1, JsonElement element2)
{
-#if NET9_0_OR_GREATER
return JsonElement.DeepEquals(element1, element2);
-#else
- return JsonNode.DeepEquals(
- JsonSerializer.SerializeToNode(element1, AIJsonUtilities.DefaultOptions),
- JsonSerializer.SerializeToNode(element2, AIJsonUtilities.DefaultOptions));
-#endif
}
private static void AssertDeepEquals(JsonElement element1, JsonElement element2)
diff --git a/test/Libraries/Microsoft.Extensions.AI.Tests/ChatCompletion/ChatClientStructuredOutputExtensionsTests.cs b/test/Libraries/Microsoft.Extensions.AI.Tests/ChatCompletion/ChatClientStructuredOutputExtensionsTests.cs
index 431b2053d62..1f7e7f5084a 100644
--- a/test/Libraries/Microsoft.Extensions.AI.Tests/ChatCompletion/ChatClientStructuredOutputExtensionsTests.cs
+++ b/test/Libraries/Microsoft.Extensions.AI.Tests/ChatCompletion/ChatClientStructuredOutputExtensionsTests.cs
@@ -482,12 +482,6 @@ Elements are not equal.
private static bool DeepEquals(JsonElement element1, JsonElement element2)
{
-#if NET9_0_OR_GREATER
return JsonElement.DeepEquals(element1, element2);
-#else
- return System.Text.Json.Nodes.JsonNode.DeepEquals(
- JsonSerializer.SerializeToNode(element1, AIJsonUtilities.DefaultOptions),
- JsonSerializer.SerializeToNode(element2, AIJsonUtilities.DefaultOptions));
-#endif
}
}
diff --git a/test/Shared/JsonSchemaExporter/JsonSchemaExporterConfigurationTests.cs b/test/Shared/JsonSchemaExporter/JsonSchemaExporterConfigurationTests.cs
deleted file mode 100644
index 1d2b6caa74e..00000000000
--- a/test/Shared/JsonSchemaExporter/JsonSchemaExporterConfigurationTests.cs
+++ /dev/null
@@ -1,35 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-using System.Text.Json.Schema;
-using Xunit;
-
-namespace Microsoft.Extensions.AI.JsonSchemaExporter;
-
-public static class JsonSchemaExporterConfigurationTests
-{
- [Theory]
- [InlineData(false)]
- [InlineData(true)]
- public static void JsonSchemaExporterOptions_DefaultValues(bool useSingleton)
- {
- JsonSchemaExporterOptions configuration = useSingleton ? JsonSchemaExporterOptions.Default : new();
- Assert.False(configuration.TreatNullObliviousAsNonNullable);
- Assert.Null(configuration.TransformSchemaNode);
- }
-
- [Fact]
- public static void JsonSchemaExporterOptions_Singleton_ReturnsSameInstance()
- {
- Assert.Same(JsonSchemaExporterOptions.Default, JsonSchemaExporterOptions.Default);
- }
-
- [Theory]
- [InlineData(false)]
- [InlineData(true)]
- public static void JsonSchemaExporterOptions_TreatNullObliviousAsNonNullable(bool treatNullObliviousAsNonNullable)
- {
- JsonSchemaExporterOptions configuration = new() { TreatNullObliviousAsNonNullable = treatNullObliviousAsNonNullable };
- Assert.Equal(treatNullObliviousAsNonNullable, configuration.TreatNullObliviousAsNonNullable);
- }
-}
diff --git a/test/Shared/JsonSchemaExporter/JsonSchemaExporterTests.cs b/test/Shared/JsonSchemaExporter/JsonSchemaExporterTests.cs
deleted file mode 100644
index c83c50c1f2e..00000000000
--- a/test/Shared/JsonSchemaExporter/JsonSchemaExporterTests.cs
+++ /dev/null
@@ -1,180 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-using System;
-using System.Collections.Generic;
-using System.Collections.Immutable;
-using System.Text.Json;
-using System.Text.Json.Nodes;
-using System.Text.Json.Schema;
-using System.Text.Json.Serialization;
-using System.Text.Json.Serialization.Metadata;
-#if !NET9_0_OR_GREATER
-using System.Xml.Linq;
-#endif
-using Xunit;
-using static Microsoft.Extensions.AI.JsonSchemaExporter.TestTypes;
-
-#pragma warning disable SA1402 // File may only contain a single type
-
-namespace Microsoft.Extensions.AI.JsonSchemaExporter;
-
-public abstract class JsonSchemaExporterTests
-{
- protected abstract JsonSerializerOptions Options { get; }
-
- [Theory]
- [MemberData(nameof(TestTypes.GetTestData), MemberType = typeof(TestTypes))]
- public void TestTypes_GeneratesExpectedJsonSchema(ITestData testData)
- {
- JsonSerializerOptions options = testData.Options is { } opts
- ? new(opts) { TypeInfoResolver = Options.TypeInfoResolver }
- : Options;
-
- JsonNode schema = options.GetJsonSchemaAsNode(testData.Type, (JsonSchemaExporterOptions?)testData.ExporterOptions);
- SchemaTestHelpers.AssertEqualJsonSchema(testData.ExpectedJsonSchema, schema);
- }
-
- [Theory]
- [MemberData(nameof(TestTypes.GetTestDataUsingAllValues), MemberType = typeof(TestTypes))]
- public void TestTypes_SerializedValueMatchesGeneratedSchema(ITestData testData)
- {
- JsonSerializerOptions options = testData.Options is { } opts
- ? new(opts) { TypeInfoResolver = Options.TypeInfoResolver }
- : Options;
-
- JsonNode schema = options.GetJsonSchemaAsNode(testData.Type, (JsonSchemaExporterOptions?)testData.ExporterOptions);
- JsonNode? instance = JsonSerializer.SerializeToNode(testData.Value, testData.Type, options);
- SchemaTestHelpers.AssertDocumentMatchesSchema(schema, instance);
- }
-
- [Theory]
- [InlineData(typeof(string), "string")]
- [InlineData(typeof(int[]), "array")]
- [InlineData(typeof(Dictionary), "object")]
- [InlineData(typeof(TestTypes.SimplePoco), "object")]
- public void TreatNullObliviousAsNonNullable_True_MarksAllReferenceTypesAsNonNullable(Type referenceType, string expectedType)
- {
- Assert.True(!referenceType.IsValueType);
- var config = new JsonSchemaExporterOptions { TreatNullObliviousAsNonNullable = true };
- JsonNode schema = Options.GetJsonSchemaAsNode(referenceType, config);
- JsonValue type = Assert.IsAssignableFrom(schema["type"]);
- Assert.Equal(expectedType, (string)type!);
- }
-
- [Theory]
- [InlineData(typeof(int), "integer")]
- [InlineData(typeof(double), "number")]
- [InlineData(typeof(bool), "boolean")]
- [InlineData(typeof(ImmutableArray), "array")]
- [InlineData(typeof(TestTypes.StructDictionary), "object")]
- [InlineData(typeof(TestTypes.SimpleRecordStruct), "object")]
- public void TreatNullObliviousAsNonNullable_True_DoesNotImpactNonReferenceTypes(Type referenceType, string expectedType)
- {
- Assert.True(referenceType.IsValueType);
- var config = new JsonSchemaExporterOptions { TreatNullObliviousAsNonNullable = true };
- JsonNode schema = Options.GetJsonSchemaAsNode(referenceType, config);
- JsonValue value = Assert.IsAssignableFrom(schema["type"]);
- Assert.Equal(expectedType, (string)value!);
- }
-
-#if !NET9_0_OR_GREATER // Disable until https://github.com/dotnet/runtime/pull/108764 gets backported
- [Fact]
- public void CanGenerateXElementSchema()
- {
- JsonNode schema = Options.GetJsonSchemaAsNode(typeof(XElement));
- Assert.True(schema.ToJsonString().Length < 100_000);
- }
-#endif
-
-#if !NET9_0_OR_GREATER // Disable until https://github.com/dotnet/runtime/pull/109954 gets backported
- [Fact]
- public void TransformSchemaNode_PropertiesWithCustomConverters()
- {
- // Regression test for https://github.com/dotnet/runtime/issues/109868
- List<(Type? parentType, string? propertyName, Type type)> visitedNodes = new();
- JsonSchemaExporterOptions exporterOptions = new()
- {
- TransformSchemaNode = (ctx, schema) =>
- {
-#if NET9_0_OR_GREATER
- visitedNodes.Add((ctx.PropertyInfo?.DeclaringType, ctx.PropertyInfo?.Name, ctx.TypeInfo.Type));
-#else
- visitedNodes.Add((ctx.DeclaringType, ctx.PropertyInfo?.Name, ctx.TypeInfo.Type));
-#endif
- return schema;
- }
- };
-
- List<(Type? parentType, string? propertyName, Type type)> expectedNodes =
- [
- (typeof(ClassWithPropertiesUsingCustomConverters), "Prop1", typeof(ClassWithPropertiesUsingCustomConverters.ClassWithCustomConverter1)),
- (typeof(ClassWithPropertiesUsingCustomConverters), "Prop2", typeof(ClassWithPropertiesUsingCustomConverters.ClassWithCustomConverter2)),
- (null, null, typeof(ClassWithPropertiesUsingCustomConverters)),
- ];
-
- Options.GetJsonSchemaAsNode(typeof(ClassWithPropertiesUsingCustomConverters), exporterOptions);
-
- Assert.Equal(expectedNodes, visitedNodes);
- }
-#endif
-
- [Fact]
- public void TreatNullObliviousAsNonNullable_True_DoesNotImpactObjectType()
- {
- var config = new JsonSchemaExporterOptions { TreatNullObliviousAsNonNullable = true };
- JsonNode schema = Options.GetJsonSchemaAsNode(typeof(object), config);
- Assert.False(schema is JsonObject jObj && jObj.ContainsKey("type"));
- }
-
- [Fact]
- public void TypeWithDisallowUnmappedMembers_AdditionalPropertiesFailValidation()
- {
- JsonNode schema = Options.GetJsonSchemaAsNode(typeof(TestTypes.PocoDisallowingUnmappedMembers));
- JsonNode? jsonWithUnmappedProperties = JsonNode.Parse("""{ "UnmappedProperty" : {} }""");
- SchemaTestHelpers.AssertDoesNotMatchSchema(schema, jsonWithUnmappedProperties);
- }
-
- [Fact]
- public void GetJsonSchema_NullInputs_ThrowsArgumentNullException()
- {
- Assert.Throws(() => ((JsonSerializerOptions)null!).GetJsonSchemaAsNode(typeof(int)));
- Assert.Throws(() => Options.GetJsonSchemaAsNode(type: null!));
- Assert.Throws(() => ((JsonTypeInfo)null!).GetJsonSchemaAsNode());
- }
-
- [Fact]
- public void GetJsonSchema_NoResolver_ThrowInvalidOperationException()
- {
- var options = new JsonSerializerOptions();
- Assert.Throws(() => options.GetJsonSchemaAsNode(typeof(int)));
- }
-
- [Fact]
- public void MaxDepth_SetToZero_NonTrivialSchema_ThrowsInvalidOperationException()
- {
- JsonSerializerOptions options = new(Options) { MaxDepth = 1 };
- var ex = Assert.Throws(() => options.GetJsonSchemaAsNode(typeof(TestTypes.SimplePoco)));
- Assert.Contains("The depth of the generated JSON schema exceeds the JsonSerializerOptions.MaxDepth setting.", ex.Message);
- }
-
- [Fact]
- public void ReferenceHandlePreserve_Enabled_ThrowsNotSupportedException()
- {
- var options = new JsonSerializerOptions(Options) { ReferenceHandler = ReferenceHandler.Preserve };
- options.MakeReadOnly();
-
- var ex = Assert.Throws(() => options.GetJsonSchemaAsNode(typeof(TestTypes.SimplePoco)));
- Assert.Contains("ReferenceHandler.Preserve", ex.Message);
- }
-}
-
-public sealed class ReflectionJsonSchemaExporterTests : JsonSchemaExporterTests
-{
- protected override JsonSerializerOptions Options => JsonSerializerOptions.Default;
-}
-
-public sealed class SourceGenJsonSchemaExporterTests : JsonSchemaExporterTests
-{
- protected override JsonSerializerOptions Options => TestTypes.TestTypesContext.Default.Options;
-}
diff --git a/test/Shared/Shared.Tests.csproj b/test/Shared/Shared.Tests.csproj
index b7e27306f2a..2764d5f5d5d 100644
--- a/test/Shared/Shared.Tests.csproj
+++ b/test/Shared/Shared.Tests.csproj
@@ -2,7 +2,6 @@
Microsoft.Shared.Test
Unit tests for Microsoft.Shared
- $(DefineConstants);TESTS_JSON_SCHEMA_EXPORTER_POLYFILL